#!/usr/bin/python
+
import os
+import shutil
+import smtplib
import urlgrabber
+import urlgrabber.progress
import urllib
import chroot
import util
+from exception import *
from constants import *
__cache = {
"group_names" : None,
}
-def find_package_info(name, toolchain=False):
+# Python 2.4 does not have that email module, so
+# we disable the mail function here.
+try:
+ import email.mime.multipart
+ import email.mime.text
+ have_email = 1
+except ImportError:
+ have_email = 0
+
+try:
+ import hashlib
+ have_hashlib = 1
+except ImportError:
+ import sha
+ have_hashlib = 0
+
+def find_package_info(name, toolchain=False, **kwargs):
for repo in get_repositories(toolchain):
if not os.path.exists(os.path.join(repo.path, name, name + ".nm")):
continue
- return PackageInfo(name, repo=repo)
+ return PackageInfo(name, repo=repo, **kwargs)
-def find_package(name, toolchain=False):
+def find_package(name, naoki, toolchain=False):
package = find_package_info(name, toolchain)
if package:
- package = backend.Package(package)
+ return package.getPackage(naoki)
- return package
+ return None
-def parse_package_info(names, toolchain=False):
+def parse_package_info(names, toolchain=False, **kwargs):
packages = []
for name in names:
- package = find_package_info(name, toolchain)
+ package = find_package_info(name, toolchain, **kwargs)
if package:
packages.append(package)
def parse_package(names, toolchain=False, naoki=None):
packages = parse_package_info(names, toolchain)
- return [Package(package.name, naoki=naoki) for package in packages]
+ return [Package(package.name, naoki=naoki, toolchain=toolchain) \
+ for package in packages]
def get_package_names(toolchain=False):
if not __cache["package_names"]:
if os.path.basename(package) == name:
return package
-def depsolve(packages, recursive=False):
+def depsolve(packages, recursive=False, build=False, toolchain=False):
deps = []
for package in packages:
if not package in deps:
length = len(deps)
for dep in deps[:]:
deps.extend(dep.dependencies)
+ if build and not toolchain:
+ deps.extend(dep.dependencies_build)
new_deps = []
for dep in deps:
deps.sort()
return deps
-def deptree(packages):
+def deptree(packages, toolchain=False):
ret = [packages]
while True:
next = []
stage = ret[-1][:]
for package in stage[:]:
- for dep in package.info.dependencies_all:
+ for dep in package.dependencies_all:
if dep in ret[-1]:
stage.remove(package)
next.append(package)
return ret
-def depsort(packages):
- ret = []
- for l1 in deptree(packages):
+def depsort(packages, toolchain=False):
+ ret = []
+ for l1 in deptree(packages, toolchain=toolchain):
ret.extend(l1)
return ret
+def calc_hash(data):
+ if have_hashlib:
+ obj = hashlib.sha1(data)
+ else:
+ obj = sha.new(data)
+
+ return obj.hexdigest()
+
def download(files, logger=None):
for file in files:
filepath = os.path.join(TARBALLDIR, file)
try:
gobj = g.urlopen(url)
except urlgrabber.grabber.URLGrabError, e:
- logger.error("Could not retrieve %s - %s" % (url, e))
+ if logger:
+ logger.error("Could not retrieve %s - %s" % (url, e))
raise
- # XXX Need to check SHA1 sum here
+ data = gobj.read()
+ gobj.close()
+
+ if gobj.hdr.has_key("X-Hash-Sha1"):
+ hash_server = gobj.hdr["X-Hash-Sha1"]
+ msg = "Comparing hashes - %s" % hash_server
+
+ hash_calculated = calc_hash(data)
+ if hash_calculated == hash_server:
+ if logger:
+ logger.debug(msg + " - OK")
+ else:
+ if logger:
+ logger.error(msg + " - ERROR")
+ raise DownloadError, "Hash sum of downloaded file does not match"
fobj = open(filepath, "w")
- fobj.write(gobj.read())
+ fobj.write(data)
fobj.close()
- gobj.close()
+
class PackageInfo(object):
__data = {}
- def __init__(self, name, repo=None):
+ def __init__(self, name, repo=None, arch=arches.current["name"]):
self._name = name
self.repo = repo
+ self.arch = arch
+
+ def __cmp__(self, other):
+ return cmp(self.name, other.name)
+
def __repr__(self):
return "<PackageInfo %s>" % self.name
def fetch(self):
env = os.environ.copy()
env.update(config.environment)
- env["PKGROOT"] = PKGSDIR
+ env.update({
+ "PKG_ARCH" : self.arch,
+ "PKGROOT" : PKGSDIR,
+ })
output = util.do("make -f %s" % self.filename, shell=True,
cwd=os.path.join(PKGSDIR, self.repo.name, self.name), returnOutput=1, env=env)
@property
def all(self):
return {
+ "build_deps" : [dep.name for dep in self.dependencies_build],
+ "deps" : [dep.name for dep in self.dependencies],
"description" : self.description,
"filename" : self.filename,
"fingerprint" : self.fingerprint,
+ "files" : self.package_files,
"group" : self.group,
+ "id" : self.id,
"license" : self.license,
"maintainer" : self.maintainer,
"name" : self.name,
"patches" : self.patches,
"release" : self.release,
"summary" : self.summary,
+ "url" : self.url,
"version" : self.version,
}
- def _dependencies(self, s, recursive=False):
+ @property
+ def buildable(self):
+ return self.dependencies_unbuilt == []
+
+ @property
+ def built(self):
+ for file in self.package_files:
+ if not os.path.exists(os.path.join(PACKAGESDIR, file)):
+ return False
+
+ return True
+
+ def _dependencies(self, s, recursive=False, toolchain=False):
c = s + "_CACHE"
if not self._data.has_key(c):
- deps = parse_package_info(self._data.get(s).split(" "))
+ deps = parse_package_info(self._data.get(s).split(" "), toolchain=toolchain)
self._data.update({c : depsolve(deps, recursive)})
return self._data.get(c)
@property
def dependencies(self):
+ if self.__toolchain:
+ return self.dependencies_toolchain
+
return self._dependencies("PKG_DEPENDENCIES")
@property
def dependencies_build(self):
return self._dependencies("PKG_BUILD_DEPENDENCIES")
+ @property
+ def dependencies_built(self):
+ ret = []
+ for dep in self.dependencies_all:
+ if dep.built:
+ ret.append(dep)
+
+ return ret
+
+ @property
+ def dependencies_unbuilt(self):
+ ret = []
+ for dep in self.dependencies_all:
+ if not dep.built:
+ ret.append(dep)
+
+ return ret
+
@property
def dependencies_all(self):
- return depsolve(self.dependencies + self.dependencies_build, recursive=True)
+ deps = self.dependencies
+ if not self.__toolchain:
+ deps.extend(self.dependencies_build)
+ return depsolve(deps, build=True, recursive=True, toolchain=self.__toolchain)
+
+ @property
+ def dependencies_toolchain(self):
+ return self._dependencies("PKG_TOOLCHAIN_DEPENDENCIES", toolchain=True)
@property
def description(self):
@property
def fingerprint(self):
- return "%d" % os.stat(self.filename).st_mtime
+ return "%d" % self.last_change
@property
def group(self):
def id(self):
return "%s-%s-%s" % (self.name, self.version, self.release)
+ @property
+ def last_build(self):
+ file = os.path.join(PACKAGESDIR, self.package_files[0])
+ if not os.path.exists(file):
+ return 0
+
+ return os.stat(file).st_mtime
+
+ @property
+ def last_change(self):
+ return os.stat(self.filename).st_mtime
+
@property
def license(self):
return self._data.get("PKG_LICENSE")
def summary(self):
return self._data.get("PKG_SUMMARY")
+ @property
+ def url(self):
+ return self._data.get("PKG_URL")
+
@property
def version(self):
return self._data.get("PKG_VER")
+ @property
+ def __toolchain(self):
+ return self.repo.name == "toolchain"
+
class Package(object):
- def __init__(self, name, naoki):
- self.info = find_package_info(name)
+ def __init__(self, name, naoki, toolchain=False):
+ self.info = find_package_info(name, toolchain)
+
+ assert naoki
self.naoki = naoki
#self.log.debug("Initialized package object %s" % name)
def __repr__(self):
return "<Package %s>" % self.info.name
+ def __cmp__(self, other):
+ return cmp(self.name, other.name)
+
+ def __getattr__(self, attr):
+ return getattr(self.info, attr)
+
+ @property
+ def name(self):
+ return self.info.name
+
def build(self):
- environment = chroot.Environment(self)
+ environment = chroot.PackageEnvironment(self)
environment.build()
def download(self):
util.do("%s --root=%s %s" % (os.path.join(TOOLSDIR, "decompressor"),
dest, " ".join(files)), shell=True)
+ def getEnvironment(self, *args, **kwargs):
+ return chroot.PackageEnvironment(self, *args, **kwargs)
+
@property
def log(self):
- return self.naoki.logging.getBuildLogger(self.info.id)
+ return self.naoki.logging.getBuildLogger(os.path.join(self.repo.name, self.info.id))
def get_repositories(toolchain=False):
if toolchain:
- return Repository("toolchain")
+ return [Repository("toolchain")]
repos = []
for repo in os.listdir(PKGSDIR):
def path(self):
return os.path.join(PKGSDIR, self.name)
-if __name__ == "__main__":
- pi = PackageInfo("core/grub")
- print pi.dependencies
+class BinaryRepository(object):
+ DIRS = ("db", "packages")
+
+ def __init__(self, name, naoki=None, arch=None):
+ self.name = name
+ self.arch = arch or arches.current
+ self.repo = Repository(self.name)
+
+ assert naoki
+ self.naoki = naoki
+
+ def build(self):
+ if not self.buildable:
+ raise Exception, "Cannot build repository"
+
+ # Create temporary directory layout
+ util.rm(self.repopath("tmp"))
+ for dir in self.DIRS:
+ util.mkdir(self.repopath("tmp", dir))
+
+ # Copy packages
+ for package in self.packages:
+ for file in package.package_files:
+ shutil.copy(os.path.join(PACKAGESDIR, file),
+ self.repopath("tmp", "packages"))
+
+ # TODO check repository's sanity
+ # TODO create repoview
+ f = open(self.repopath("tmp", "db", "package-list.txt"), "w")
+ for package in self.packages:
+ s = "%-40s" % package.fmtstr("%(name)s-%(version)s-%(release)s")
+ s += " | %s\n" % package.summary
+ f.write(s)
+ f.close()
+
+ for dir in self.DIRS:
+ util.rm(self.repopath(dir))
+ shutil.move(self.repopath("tmp", dir), self.repopath(dir))
+ util.rm(self.repopath("tmp"))
+
+ def clean(self):
+ if os.path.exists(self.path):
+ self.log.debug("Cleaning up repository: %s" % self.path)
+ util.rm(self.path)
+
+ def repopath(self, *args):
+ return os.path.join(self.path, *args)
+
+ @property
+ def buildable(self):
+ for package in self.packages:
+ if package.built:
+ continue
+ return False
+
+ return True
+
+ @property
+ def log(self):
+ return self.naoki.log
+
+ @property
+ def packages(self):
+ packages = []
+ for package in parse_package_info(get_package_names(), arch=self.arch["name"]):
+ if not package.repo.name == self.name:
+ continue
+ packages.append(package)
+ return packages
+
+ @property
+ def path(self):
+ return os.path.join(REPOSDIR, self.name, self.arch["name"])
+
+def report_error_by_mail(package):
+ log = package.naoki.log
+
+ # Do not send a report if no recipient is configured
+ if not config["error_report_recipient"]:
+ return
+
+ if not have_email:
+ log.error("Can't send mail because this python version does not support this")
+ return
+
+ try:
+ connection = smtplib.SMTP(config["smtp_server"])
+ #connection.set_debuglevel(1)
+
+ if config["smtp_user"] and config["smtp_password"]:
+ connection.login(config["smtp_user"], config["smtp_password"])
+
+ except SMTPConnectError, e:
+ log.error("Could not establish a connection to the smtp server: %s" % e)
+ return
+ except SMTPAuthenticationError, e:
+ log.error("Could not successfully login to the smtp server: %s" % e)
+ return
+
+ msg = email.mime.multipart.MIMEMultipart()
+ msg["From"] = config["error_report_sender"]
+ msg["To"] = config["error_report_recipient"]
+ msg["Subject"] = config["error_report_subject"] % package.all
+ msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
+
+ body = """\
+The package %(name)s had a difficulty to build itself.
+This email will give you a short report about the error.
+
+Package information:
+ Name : %(name)s - %(summary)s
+ Version : %(version)s
+ Release : %(release)s
+
+ This package in maintained by %(maintainer)s.
+
+
+A detailed logfile is attached.
+
+Sincerely,
+ Naoki
+ """ % package.all
+
+ msg.attach(email.mime.text.MIMEText(body))
+
+ # Read log and append it to mail
+ logfile = os.path.join(LOGDIR, package.id + ".log")
+ if os.path.exists(logfile):
+ log = []
+ f = open(logfile)
+ line = f.readline()
+ while line:
+ line = line.rstrip("\n")
+ if line.endswith(LOG_MARKER):
+ # Reset log
+ log = []
+
+ log.append(line)
+ line = f.readline()
+
+ f.close()
+
+ log = email.mime.text.MIMEText("\n".join(log), _subtype="plain")
+ log.add_header('Content-Disposition', 'attachment',
+ filename="%s.log" % package.id)
+ msg.attach(log)
+
+ try:
+ connection.sendmail(config["error_report_sender"],
+ config["error_report_recipient"], msg.as_string())
+ except Exception, e:
+ log.error("Could not send error report: %s: %s" % (e.__class__.__name__, e))
+ return
+
+ connection.quit()