Files
pisi/pisi/util.py
T
A. Murat Eren e2df90ae07 fixex #2559
--create-debug opsiyonu ile build esnasında oluşturulacak debug dosyaları on-the-fly oluşturulan bir PAKET-debug.pisi paketine doldurulacak. sonra oynarsınız onla işte.

 daha sonra tekrar dönülüp temizlenecek bir iki FIXME var kod içerisinde, hemen kızmayın ;)
2006-04-16 22:26:05 +00:00

659 lines
20 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
#
# Copyright (C) 2005, TUBITAK/UEKAE
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#
# misc. utility functions, including process and file utils
# Authors: Eray Ozkural <eray@pardus.org.tr>
# Baris Metin <baris@pardus.org.tr>
# S. Caglar Onur <caglar@pardus.org.tr>
# A. Murat Eren <meren@pardus.org.tr>
# standard python modules
import os
import re
import sys
import sha
import shutil
import string
import statvfs
import operator
import gettext
__trans = gettext.translation('pisi', fallback=True)
_ = __trans.ugettext
# pisi modules
import pisi
import pisi.context as ctx
class Error(pisi.Error):
pass
class FileError(Error):
pass
#########################
# spec validation utility #
#########################
class Checks:
def __init__(self):
self.list = []
def add(self, err):
self.list.append(err)
def join(self, list):
self.list.extend(list)
def has_tag(self, var, section, name):
if not var:
self.list.append(_("%s section should have a '%s' tag") % (section, name))
def has_error():
return len(self.list)>0
def print_errors(list):
for x in list:
ctx.ui.error(x)
print_errors = staticmethod(print_errors)
#########################
# string/list/functional#
#########################
def every(pred, seq):
return reduce(operator.and_, map(pred, seq))
def any(pred, seq):
return reduce(operator.or_, map(pred, seq))
def unzip(seq):
return zip(*seq)
def concat(l):
'''concatenate a list of lists'''
return reduce( operator.concat, l )
def strlist(l):
"""concatenate string reps of l's elements"""
return "".join(map(lambda x: str(x) + ' ', l))
def multisplit(str, chars):
""" split str with any of chars"""
l = [str]
for c in chars:
l = concat(map(lambda x:x.split(c), l))
return l
def same(l):
'''check if all elements of a sequence are equal'''
if len(l)==0:
return True
else:
last = l.pop()
for x in l:
if x!=last:
return False
return True
def prefix(a, b):
'''check if sequence a is a prefix of sequence b'''
if len(a)>len(b):
return False
for i in range(0,len(a)):
if a[i]!=b[i]:
return False
return True
def remove_prefix(a,b):
"remove prefix a from sequence b"
assert prefix(a,b)
return b[len(a):]
def human_readable_size(size = 0):
symbols, depth = [' B', 'KB', 'MB', 'GB'], 0
while size > 1000 and depth < 3:
size = float(size / 1024)
depth += 1
return size, symbols[depth]
def human_readable_rate(size = 0):
x = human_readable_size(size)
return x[0], x[1] + '/s'
##############################
# Process Releated Functions #
##############################
def run_batch(cmd, realtime = False):
"""run command non-interactively/realtime and report return value and output"""
ctx.ui.info(_('Running ') + cmd, verbose=True)
p = os.popen(cmd)
if realtime:
while 1:
line = p.readline()
if not line:
break
ctx.ui.debug(line[:-1])
return p.close()
else:
lines = p.readlines()
ret = p.close()
ctx.ui.debug(_('return value %s') % ret)
successful = ret == None
if not successful:
ctx.ui.error(_('Failed command: %s') % cmd + strlist(lines))
return (successful,lines)
######################
# Terminal functions #
######################
def has_xterm():
return os.environ.has_key("TERM") and sys.stderr.isatty()
def xterm_title(message):
"""sets message as a console window's title"""
if os.environ.has_key("TERM") and sys.stderr.isatty():
terminalType = os.environ["TERM"]
for term in ["xterm", "Eterm", "aterm", "rxvt", "screen", "kterm", "rxvt-unicode"]:
if terminalType.startswith(term):
sys.stderr.write("\x1b]2;"+str(message)+"\x07")
sys.stderr.flush()
break
def xterm_title_reset():
"""resets console window's title"""
if os.environ.has_key("TERM"):
terminalType = os.environ["TERM"]
xterm_title(os.environ["TERM"])
#############################
# Path Processing Functions #
#############################
def splitpath(a):
"""split path into components and return as a list
os.path.split doesn't do what I want like removing trailing /"""
comps = a.split(os.path.sep)
if comps[len(comps)-1]=='':
comps.pop()
return comps
def makepath(comps, relative = False, sep = os.path.sep):
"""reconstruct a path from components"""
path = reduce(lambda x,y: x + sep + y, comps, '')
if relative:
return path[len(sep):]
else:
return path
def parentpath(a, sep = os.path.sep):
# remove trailing '/'
a = a.rstrip(sep)
return a[:a.rfind(sep)]
def parenturi(a):
return parentpath(a, '/')
# I'm not sure how necessary this is. Ahem.
def commonprefix(l):
"""an improved version of os.path.commonprefix,
returns a list of path components"""
common = []
comps = map(splitpath, l)
for i in range(0, min(len,l)):
compi = map(lambda x: x[i], comps) # get ith slice
if same(compi):
common.append(compi[0])
return common
# but this one is necessary
def subpath(a, b):
"find if path a is before b in the directory tree"
return prefix(splitpath(a), splitpath(b))
def removepathprefix(prefix, path):
"remove path prefix a from b, finding the pathname rooted at a"
comps = remove_prefix(splitpath(prefix), splitpath(path))
if len(comps) > 0:
return join_path(*tuple(comps))
else:
return ""
def absolute_path(path):
"determine if given @path is absolute"
comps = splitpath(path)
return comps[0] == ''
def join_path(a, *p):
"""Join two or more pathname components, inserting '/' as needed"""
"""The python original version has a silly logic"""
path = a
for b in p:
b = b.lstrip('/')
if path == '' or path.endswith('/'):
path += b
else:
path += '/' + b
return path
####################################
# File/Directory Related Functions #
####################################
def check_file(file, mode = os.F_OK):
"shorthand to check if a file exists"
if not os.access(file, mode):
raise FileError("File " + file + " not found")
return True
def check_dir(dir):
"""check if directory exists, and create if it doesn't.
works recursively"""
dir = dir.strip().rstrip("/")
if not os.access(dir, os.F_OK):
os.makedirs(dir)
def clean_dir(path):
"Remove all content of a directory (top)"
# don't reimplement the wheel
if os.path.exists(path):
shutil.rmtree(path)
def creation_time(file):
"""returns the creation time of the given file"""
if check_file(file):
import time
st = os.stat(file)
return time.localtime(st.st_ctime)
def dir_size(dir):
""" calculate the size of files under a dir
based on the os module example"""
# It's really hard to give an approximate value for package's
# installed size. Gettin a sum of all files' sizes if far from
# being true. Using 'du' command (like Debian does) can be a
# better solution :(.
# Not really, du calculates size on disk, this is much better -- exa
from os.path import getsize, islink, isdir, exists
join = join_path
if exists(dir) and (not isdir(dir) and not islink(dir)):
#so, this is not a directory but file..
return getsize(dir)
if islink(dir):
return long(len(os.readlink(dir)))
def sizes():
for root, dirs, files in os.walk(dir):
yield sum([getsize(join(root, name)) for name in files if not islink(join(root,name))])
yield sum([long(len(os.readlink((join(root, name))))) for name in files if islink(join(root,name))])
return sum( sizes() )
def copy_file(src,dest):
"""copy source file to destination file"""
check_file(src)
check_dir(os.path.dirname(dest))
shutil.copyfile(src, dest)
def is_ar_file(file_path):
return open(file_path).readline().strip() == '!<arch>'
def clean_ar_timestamps(ar_file):
"""clean the timestamps of the ar files"""
if not is_ar_file(ar_file):
return
content = open(ar_file).readlines()
fp = open(ar_file, 'w')
for line in content:
pos = line.rfind(chr(32) + chr(96))
if pos > -1 and line[pos - 57:pos + 2].find(chr(47)) > -1:
line = line[:pos - 41] + '0000000000' + line[pos - 31:]
fp.write(line)
fp.close()
# FIXME: this should be done in a much much simpler way
# as it stands, it seems to be a kludge to solve
# an unrelated problem
def get_file_hashes(top, excludePrefix=None, removePrefix=None):
"""Generator function iterates over a toplevel path and returns the
(filePath, sha1Hash) tuples for all files. If excludePrefixes list
is given as a parameter, function will exclude the filePaths
matching those prefixes. The removePrefix string parameter will be
used to remove prefix from filePath while matching excludes, if
given."""
def sha1_sum(f, data=False):
if not data and f.endswith('.a'):
#workaround for .a issue..
#don't skip .a files,
#but pad their timestamps with '0'..
clean_ar_timestamps(f)
func = None
if data:
func = sha1_data
else:
func = sha1_file
try:
return func(f)
except FileError, e:
if os.path.islink(f):
ctx.ui.info(_("Including external link '%s'") % f)
elif os.path.isdir(f):
ctx.ui.info(_("Including directory '%s'") % f)
else:
raise e
return None
def has_excluded_prefix(filename):
if excludePrefix and removePrefix:
tempfnam = remove_prefix(removePrefix, filename)
for p in excludePrefix:
if tempfnam.startswith(p):
return 1
return 0
# handle single file
if os.path.isfile(top):
yield (top, sha1_sum(top))
return
# handle single symlink declaration here.
if os.path.islink(top):
yield (top, sha1_sum(os.readlink(top), True))
return
for root, dirs, files in os.walk(top, topdown=False):
#bug 339
if os.path.islink(root) and not has_excluded_prefix(root):
#yield the symlink..
#bug 373
yield (root, sha1_sum(os.readlink(root), True))
excludePrefix.append(remove_prefix(removePrefix, root) + "/")
continue
#bug 397
for dir in dirs:
d = join_path(root, dir)
if os.path.islink(d) and not has_excluded_prefix(d):
yield (d, sha1_sum(os.readlink(d), True))
excludePrefix.append(remove_prefix(removePrefix, d) + "/")
#bug 340
if os.path.isdir(root) and not has_excluded_prefix(root):
parent, r, d, f = root, '', '', ''
for r, d, f in os.walk(parent, topdown=False): pass
if not f and not d:
yield (parent, sha1_sum(parent))
for fname in files:
f = join_path(root, fname)
if has_excluded_prefix(f):
continue
#bug 373
elif os.path.islink(f):
yield (f, sha1_sum(os.readlink(f), True))
else:
yield (f, sha1_sum(f))
def copy_dir(src, dest):
"""copy source dir to destination dir recursively"""
shutil.copytree(src, dest)
def check_file_hash(filename, hash):
"""Check the files integrity with a given hash"""
return sha1_file(filename) == hash
def sha1_file(filename):
"""calculate sha1 hash of filename"""
# Broken links can cause problem!
try:
m = sha.new()
f = file(filename, 'rb')
for line in f:
m.update(line)
return m.hexdigest()
except IOError:
raise FileError(_("I/O Error: Cannot calculate SHA1 hash of %s") % filename)
def sha1_data(data):
"""calculate sha1 hash of given data"""
try:
m = sha.new()
m.update(data)
return m.hexdigest()
except:
raise Error(_("Cannot calculate SHA1 hash of given data"))
def uncompress(patchFile, compressType="gz", targetDir=None):
"""uncompresses a file and returns the path of the uncompressed
file"""
if targetDir:
filePath = join_path(targetDir,
os.path.basename(patchFile))
else:
filePath = os.path.basename(patchFile)
if compressType == "gz":
from gzip import GzipFile
obj = GzipFile(patchFile)
elif compressType == "bz2":
from bz2 import BZ2File
obj = BZ2File(patchFile)
open(filePath, "w").write(obj.read())
return filePath
def do_patch(sourceDir, patchFile, level = 0, target = ''):
"""simple function to apply patches.."""
cwd = os.getcwd()
os.chdir(sourceDir)
if level == None:
level = 0
if target == None:
target = ''
check_file(patchFile)
(successful, lines) = run_batch("patch -p%d %s< %s" %
(level, target, patchFile))
if not successful:
raise Error(_("ERROR: patch (%s) failed: %s") % (patchFile,
strlist (lines)))
os.chdir(cwd)
def strip_directory(top, excludelist=[]):
for root, dirs, files in os.walk(top):
for fn in files:
frpath = join_path(root, fn)
# Some upstream sources have buggy libtool and ltmain.sh with them,
# which causes wrong path entries in *.la files. And these wrong path
# entries sometimes triggers compile-time errors or linkage problems.
# Instead of patching all these buggy sources and maintain these patches,
# PISI removes wrong paths...
extension = os.path.splitext(frpath)[1]
if extension == ".la":
# FIXME: I'm regular expr. idiot, so one can convert this to python...
os.system("sed -i -e 's~-L/var/tmp/pisi/[[:graph:]]* ~~g' %s" % frpath)
# real path in .pisi package
p = '/' + removepathprefix(top, frpath)
strip = True
for exclude in excludelist:
if p.startswith(exclude):
strip = False
ctx.ui.debug("%s [%s]" %(p, "NoStrip"))
if strip:
if strip_file(frpath):
ctx.ui.debug("%s [%s]" %(p, "stripped"))
def strip_file(filepath):
"""strip a file"""
p = os.popen("file \"%s\"" % filepath)
o = p.read()
def run_strip(f, flags=""):
p = os.popen("strip %s %s" %(flags, f))
ret = p.close()
if ret:
ctx.ui.warning(_("strip command failed for file '%s'!") % f)
def save_elf_debug(f):
"""copy debug info into file.debug file"""
p = os.popen("objcopy --only-keep-debug %s %s%s" % (f, f, ctx.const.debug_file_suffix))
ret = p.close()
if ret:
ctx.ui.warning(_("objcopy (keep-debug) command failed for file '%s'!") % f)
"""mark binary/shared objects to use file.debug"""
p = os.popen("objcopy --add-gnu-debuglink=%s%s %s" % (f, ctx.const.debug_file_suffix, f))
ret = p.close()
if ret:
ctx.ui.warning(_("objcopy (add-debuglink) command failed for file '%s'!") % f)
if "current ar archive" in o:
run_strip(filepath, "-g")
return True
elif "SB executable" in o:
if ctx.get_option('create_debug'):
save_elf_debug(filepath)
run_strip(filepath)
return True
elif "SB shared object" in o:
if ctx.get_option('create_debug'):
save_elf_debug(filepath)
run_strip(filepath, "--strip-unneeded")
# FIXME: warn for TEXTREL
return True
return False
def partition_freespace(directory):
""" returns free space of given directory's partition """
st = os.statvfs(directory)
return st[statvfs.F_BSIZE] * st[statvfs.F_BFREE]
def clean_locks(top = '.'):
for root, dirs, files in os.walk(top):
for fn in files:
if fn.endswith('.lock'):
path = join_path(root, fn)
ctx.ui.info(_('Removing lock %s'), path)
os.unlink(path)
########################################
# Package/Repository Related Functions #
########################################
def package_name(name, version, release, build, prependSuffix=True):
fn = name + '-' + version + '-' + release
if build:
fn += '-' + str(build)
if prependSuffix:
fn += ctx.const.package_suffix
return fn
def is_package_name(fn, package_name = None):
"check if fn is a valid filename for given package_name"
"if not given a package name, see if fn fits the package name rules"
if (package_name==None) or fn.startswith(package_name + '-'):
if fn.endswith(ctx.const.package_suffix):
# get version string, skip separator '-'
verstr = fn[len(package_name) + 1:
len(fn)-len(ctx.const.package_suffix)]
import string
for x in verstr.split('-'):
# weak rule: version components after '-' start with a digit
if x is '' or (not x[0] in string.digits):
return False
return True
return False
def env_update():
ctx.ui.info(_('Updating environment...'))
env_dir = join_path(ctx.config.dest_dir(), "/etc/env.d")
profile_file = join_path(ctx.config.dest_dir(), "/etc/profile.env")
ldconf_file = join_path(ctx.config.dest_dir(), "/etc/ld.so.conf")
if not os.path.exists(env_dir):
os.makedirs(env_dir, 0755)
list = []
for file in os.listdir(env_dir):
if not os.path.isdir(join_path(env_dir, file)):
list.append(file)
list.sort()
keys = {}
for file in list:
f = open(join_path(env_dir, file), "r")
for line in f:
if not re.search("^#", line.strip()):
currentLine = line.strip().split("=")
try:
if keys.has_key(currentLine[0]):
keys[currentLine[0]] += ":" + currentLine[1].replace("\"", "")
else:
keys[currentLine[0]] = currentLine[1].replace("\"", "")
except IndexError:
pass
# generate profile.env
f = open(profile_file, "w")
for key in keys:
f.write("export %s=\"%s\"\n" % (key, keys[key]))
f.close()
# generate ld.so.conf
f = open(ldconf_file, "w")
if keys.has_key("LDPATH"):
for path in keys["LDPATH"].split(":"):
f.write("%s\n" % path)
f.close()
# run ldconfig
run_batch("/sbin/ldconfig -X -r %s" % ctx.config.dest_dir())
def pure_package_name(package_name):
"return pure package name from given string"
"ex: package_name=tasma-1.0.3-5-2.pisi, returns tasma"
if package_name.endswith(ctx.const.package_suffix):
package_name = package_name.rstrip(ctx.const.package_suffix)
return '-'.join([part for part in package_name.split('-') if part[0] not in string.digits])
def generate_pisi_file(patchFile, fromFile, toFile):
run_batch("xdelta patch %s %s %s" % (patchFile, fromFile, toFile))