]> git.ipfire.org Git - people/ms/ipfire-3.x.git/blobdiff - naoki/backend.py
Merge branch 'master' of ssh://git.ipfire.org/pub/git/ipfire-3.x
[people/ms/ipfire-3.x.git] / naoki / backend.py
index e4b20220fdf973924f158e7d742b9aecc20f5716..efc8c379a5b86a5ff7d98d9e9e061d99953c46c3 100644 (file)
@@ -1,6 +1,9 @@
 #!/usr/bin/python
 
+
 import os
+import shutil
+import smtplib
 import urlgrabber
 import urlgrabber.progress
 import urllib
@@ -8,6 +11,7 @@ import urllib
 import chroot
 import util
 
+from exception import *
 from constants import *
 
 __cache = {
@@ -15,24 +19,40 @@ __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)
 
@@ -73,7 +93,7 @@ def find_package_name(name, toolchain=False):
                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:
@@ -86,6 +106,8 @@ def depsolve(packages, recursive=False):
                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:
@@ -100,14 +122,14 @@ def depsolve(packages, recursive=False):
        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)
@@ -122,12 +144,20 @@ def deptree(packages):
 
        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)
@@ -152,27 +182,42 @@ def download(files, logger=None):
                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 = arches.current["name"]
+               self.arch = arch
 
-       #def __cmp__(self, other):
-       #       return cmp(self.name, other.name)
+       def __cmp__(self, other):
+               return cmp(self.name, other.name)
 
        def __repr__(self):
                return "<PackageInfo %s>" % self.name
@@ -218,11 +263,14 @@ class PackageInfo(object):
        @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.files,
+                       "files"       : self.package_files,
                        "group"       : self.group,
+                       "id"          : self.id,
                        "license"     : self.license,
                        "maintainer"  : self.maintainer,
                        "name"        : self.name,
@@ -230,6 +278,7 @@ class PackageInfo(object):
                        "patches"     : self.patches,
                        "release"     : self.release,
                        "summary"     : self.summary,
+                       "url"         : self.url,
                        "version"     : self.version,
                }
 
@@ -245,16 +294,19 @@ class PackageInfo(object):
 
                return True
 
-       def _dependencies(self, s, recursive=False):
+       def _dependencies(self, s, recursive=False, toolchain=False):
                c = s + "_CACHE"
                if not self._data.has_key(c):
-                       deps = parse_package(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
@@ -281,7 +333,14 @@ class PackageInfo(object):
 
        @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):
@@ -294,7 +353,7 @@ class PackageInfo(object):
 
        @property
        def fingerprint(self):
-               return "%d" % os.stat(self.filename).st_mtime
+               return "%d" % self.last_change
 
        @property
        def group(self):
@@ -304,6 +363,18 @@ class PackageInfo(object):
        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")
@@ -336,14 +407,24 @@ class PackageInfo(object):
        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, toolchain=False):
                self.info = find_package_info(name, toolchain)
+
+               assert naoki
                self.naoki = naoki
 
                #self.log.debug("Initialized package object %s" % name)
@@ -357,15 +438,19 @@ class Package(object):
        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):
                download(self.info.objects, logger=self.log)
 
        def extract(self, dest):
-               files = [os.path.join(PACKAGESDIR, file) for file in self.package_files]
+               files = [os.path.join(PACKAGESDIR, file) for file in self.info.package_files]
                if not files:
                        return
 
@@ -373,9 +458,12 @@ class Package(object):
                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):
@@ -415,7 +503,159 @@ class Repository(object):
        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()