[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#698117: marked as done (unblock: rebuildd/0.4.2)



Your message dated Wed, 20 Mar 2013 09:35:48 +0000
with message-id <ca258a8402ae5bc5842b06284e5a446b@hogwarts.powdarrmonkey.net>
and subject line Re: Bug#698117: unblock: rebuildd/0.4.2
has caused the Debian Bug report #698117,
regarding unblock: rebuildd/0.4.2
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
698117: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=698117
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
User: release.debian.org@packages.debian.org
Usertags: unblock

Please unblock package rebuildd

It appears that bug #671635 is critical and makes rebuildd not working at
all. Considering the amount of time 0.4.2 spent into unstable, I think it's
safe to push this version into testing.

diff -Nru rebuildd-0.4.1/build/lib/rebuildd/Distribution.py rebuildd-0.4.2/build/lib/rebuildd/Distribution.py
--- rebuildd-0.4.1/build/lib/rebuildd/Distribution.py	1970-01-01 01:00:00.000000000 +0100
+++ rebuildd-0.4.2/build/lib/rebuildd/Distribution.py	2008-03-05 13:21:48.000000000 +0100
@@ -0,0 +1,85 @@
+# rebuildd - Debian packages rebuild tool 
+# 
+# (c) 2007 - Julien Danjou <acid@debian.org> 
+# 
+#   This software 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; version 2 dated June, 1991. 
+# 
+#   This software is distributed in the hope that it will be useful, 
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of 
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
+#   GNU General Public License for more details. 
+# 
+#   You should have received a copy of the GNU General Public License 
+#   along with this software; if not, write to the Free Software 
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA 
+# 
+
+from RebuilddConfig import RebuilddConfig
+from RebuilddLog import RebuilddLog
+from string import Template
+
+class Distribution(object):
+    """Class implementing a Debian distribution
+
+    Substitutions are done in the command strings:
+
+      $d => The distro's name
+      $a => the target architecture
+      $p => the package's name
+      $v => the package's version
+    """
+
+    def __init__(self, name, arch):
+        self.name = name
+        self.arch = arch
+
+    def get_source_cmd(self, package):
+        """Return command used for grabing source for this distribution"""
+
+        try:
+	    args = { 'd': self.name, 'a': self.arch, 'v': package.version, \
+                'p': package.name }
+            t = Template(RebuilddConfig().get('build', 'source_cmd'))
+	    return t.safe_substitute(**args)
+        except TypeError, error:
+            RebuilddLog.error("get_source_cmd has invalid format: %s" % error)
+            return None
+ 
+    def get_build_cmd(self, package):
+        """Return command used for building source for this distribution"""
+
+        # Strip epochs (x:) away
+        try:
+            index = package.version.index(":")
+            args = { 'd': self.name, 'a': self.arch, \
+                'v': package.version[index+1:], 'p': package.name }
+            t = Template(RebuilddConfig().get('build', 'build_cmd'))
+	    return t.safe_substitute(**args)
+        except ValueError:
+            pass
+
+        try:
+            args = { 'd': self.name, 'a': self.arch, \
+                'v': package.version, 'p': package.name }
+            t = Template(RebuilddConfig().get('build', 'build_cmd'))
+	    return t.safe_substitute(**args)
+        except TypeError, error:
+            RebuilddLog.error("get_build_cmd has invalid format: %s" % error)
+            return None
+
+    def get_post_build_cmd(self, package):
+        """Return command used after building source for this distribution"""
+
+        cmd = RebuilddConfig().get('build', 'post_build_cmd')
+        if cmd == '':
+            return None
+        try:
+            args = { 'd': self.name, 'a': self.arch, \
+                'v': package.version, 'p': package.name }
+            t = Template(cmd)
+	    return t.safe_substitute(**args)
+        except TypeError, error:
+            RebuilddLog.error("post_build_cmd has invalid format: %s" % error)
+            return None
diff -Nru rebuildd-0.4.1/build/lib/rebuildd/Dists.py rebuildd-0.4.2/build/lib/rebuildd/Dists.py
--- rebuildd-0.4.1/build/lib/rebuildd/Dists.py	1970-01-01 01:00:00.000000000 +0100
+++ rebuildd-0.4.2/build/lib/rebuildd/Dists.py	2007-12-03 11:47:42.000000000 +0100
@@ -0,0 +1,37 @@
+# rebuildd - Debian packages rebuild tool 
+# 
+# (c) 2007 - Julien Danjou <acid@debian.org> 
+# 
+#   This software 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; version 2 dated June, 1991. 
+# 
+#   This software is distributed in the hope that it will be useful, 
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of 
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
+#   GNU General Public License for more details. 
+# 
+#   You should have received a copy of the GNU General Public License 
+#   along with this software; if not, write to the Free Software 
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA 
+# 
+
+class Dists(object):
+    """Singleton implementing a set of Debian distributions"""
+
+    _instance = None
+    dists = []
+ 
+    def __new__(cls): 
+        if cls._instance is None: 
+           cls._instance = object.__new__(cls) 
+        return cls._instance 
+
+    def add_dist(self, dist):
+        self.dists.append(dist)
+
+    def get_dist(self, name, arch):
+        for d in self.dists:
+            if d.name == name and d.arch == arch:
+                return d
+        return None
diff -Nru rebuildd-0.4.1/build/lib/rebuildd/Enumeration.py rebuildd-0.4.2/build/lib/rebuildd/Enumeration.py
--- rebuildd-0.4.1/build/lib/rebuildd/Enumeration.py	1970-01-01 01:00:00.000000000 +0100
+++ rebuildd-0.4.2/build/lib/rebuildd/Enumeration.py	2007-11-19 20:47:35.000000000 +0100
@@ -0,0 +1,51 @@
+# rebuildd - Debian packages rebuild tool 
+# 
+# (c) 2007 - Julien Danjou <acid@debian.org> 
+# 
+#   This software 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; version 2 dated June, 1991. 
+# 
+#   This software is distributed in the hope that it will be useful, 
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of 
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
+#   GNU General Public License for more details. 
+# 
+#   You should have received a copy of the GNU General Public License 
+#   along with this software; if not, write to the Free Software 
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA 
+# 
+
+import types
+
+class Enumeration: 
+    """Simple enumeration class with reverse lookup""" 
+ 
+    def __init__(self, enumlist): 
+        self.lookup = { } 
+        self.reverse_lookup = { } 
+        val = 0 
+        for elem in enumlist: 
+            if type(elem) == types.TupleType: 
+                elem, val = elem 
+            if type(elem) != types.StringType: 
+                raise "enum name is not a string: " + elem 
+            if type(val) != types.IntType: 
+                raise "enum value is not an integer: " + val 
+            if self.lookup.has_key(elem): 
+                raise "enum name is not unique: " + elem 
+            if val in self.lookup.values(): 
+                raise "enum value is not unique for " + val 
+            self.lookup[elem] = val 
+            self.reverse_lookup[val] = elem 
+            val += 1 
+ 
+    def __getattr__(self, attr): 
+        if not self.lookup.has_key(attr): 
+            raise AttributeError 
+        return self.lookup[attr] 
+ 
+    def whatis(self, value): 
+        """Return element name for a value""" 
+ 
+        return self.reverse_lookup[value]
diff -Nru rebuildd-0.4.1/build/lib/rebuildd/__init__.py rebuildd-0.4.2/build/lib/rebuildd/__init__.py
--- rebuildd-0.4.1/build/lib/rebuildd/__init__.py	1970-01-01 01:00:00.000000000 +0100
+++ rebuildd-0.4.2/build/lib/rebuildd/__init__.py	2007-11-19 20:47:35.000000000 +0100
@@ -0,0 +1,36 @@
+# rebuildd - Debian packages rebuild tool
+#
+# (c) 2007 - Julien Danjou <acid@debian.org>
+#
+#   This software 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; version 2 dated June, 1991.
+#
+#   This software is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this software; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+
+"""rebuildd - Debian packages rebuild tool"""
+
+__author__  = "Julien Danjou <acid@debian.org>"
+__cvsid__   = "$Id$"
+__version__ = "$Rev$"[6:-2]
+__all__ = [
+        "Distribution",
+        "Enumeration",
+        "Job",
+        "JobStatus",
+        "Package",
+        "Rebuildd",
+        "RebuilddConfig",
+        "RebuilddLog",
+        "RebuilddNetworkClient",
+        "RebuilddNetworkServer",
+        "RebuilddHTTPServer"
+        ]
diff -Nru rebuildd-0.4.1/build/lib/rebuildd/Job.py rebuildd-0.4.2/build/lib/rebuildd/Job.py
--- rebuildd-0.4.1/build/lib/rebuildd/Job.py	1970-01-01 01:00:00.000000000 +0100
+++ rebuildd-0.4.2/build/lib/rebuildd/Job.py	2007-12-03 11:47:42.000000000 +0100
@@ -0,0 +1,250 @@
+# rebuildd - Debian packages rebuild tool
+#
+# (c) 2007 - Julien Danjou <acid@debian.org>
+#
+#   This software 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; version 2 dated June, 1991.
+#
+#   This software is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this software; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+
+from __future__ import with_statement
+
+import threading, subprocess, smtplib, time, os, signal, socket, select
+import sqlobject
+from email.Message import Message
+from Dists import Dists
+from JobStatus import JobStatus
+from JobStatus import FailedStatus
+from RebuilddLog import RebuilddLog
+from RebuilddConfig import RebuilddConfig
+
+__version__ = "$Rev$"
+
+class Job(threading.Thread, sqlobject.SQLObject):
+    """Class implementing a build job"""
+
+    status = sqlobject.IntCol(default=JobStatus.UNKNOWN)
+    mailto = sqlobject.StringCol(default=None)
+    package = sqlobject.ForeignKey('Package', cascade=True)
+    dist = sqlobject.StringCol(default='sid')
+    arch = sqlobject.StringCol(default='any')
+    creation_date = sqlobject.DateTimeCol(default=sqlobject.DateTimeCol.now)
+    status_changed = sqlobject.DateTimeCol(default=None)
+    build_start = sqlobject.DateTimeCol(default=None)
+    build_end = sqlobject.DateTimeCol(default=None)
+    host = sqlobject.StringCol(default=None)
+    notify = None
+
+    def __init__(self, *args, **kwargs):
+        """Init job"""
+
+        threading.Thread.__init__(self)
+        sqlobject.SQLObject.__init__(self, *args, **kwargs)
+        self.do_quit = threading.Event()
+        self.status_lock = threading.Lock()
+
+    def __setattr__(self, name, value):
+        """Override setattr to log build status changes"""
+
+        if name == "status":
+            RebuilddLog.info("Job %s for %s_%s on %s/%s changed status from %s to %s"\
+                    % (self.id, self.package.name, self.package.version, 
+                       self.dist, self.arch,
+                       JobStatus.whatis(self.status),
+                       JobStatus.whatis(value)))
+            self.status_changed = sqlobject.DateTimeCol.now()
+        sqlobject.SQLObject.__setattr__(self, name, value)
+
+    @property
+    def logfile(self):
+        """Compute and return logfile name"""
+
+        build_log_file = "%s/%s_%s-%s-%s-%s.%s.log" % (RebuilddConfig().get('log', 'logs_dir'),
+                                           self.package.name, self.package.version,
+                                           self.dist, self.arch,
+                                           self.creation_date.strftime("%Y%m%d-%H%M%S"),
+                                           self.id)
+        return build_log_file
+
+    def preexec_child(self):
+        """Start a new group process before executing child"""
+
+        os.setsid()
+
+    def run(self):
+        """Run job thread, download and build the package"""
+
+        self.build_start = sqlobject.DateTimeCol.now()
+
+        try:
+            with open(self.logfile, "w") as build_log:
+                build_log.write("Automatic build of %s_%s on %s for %s/%s by rebuildd %s\n" % \
+                                 (self.package.name, self.package.version,
+                                  self.host, self.dist, self.arch, __version__))
+                build_log.write("Build started at %s\n" % self.build_start)
+                build_log.write("******************************************************************************\n")
+        except IOError:
+            return
+
+        build_log = file(self.logfile, "a")
+
+        # we are building
+        with self.status_lock:
+            self.status = JobStatus.BUILDING
+
+        # execute commands
+        for cmd, failed_status in ([Dists().get_dist(self.dist, self.arch).get_source_cmd(self.package),
+                                    JobStatus.SOURCE_FAILED],
+                                   [Dists().get_dist(self.dist, self.arch).get_build_cmd(self.package),
+                                    JobStatus.BUILD_FAILED],
+                                   [Dists().get_dist(self.dist, self.arch).get_post_build_cmd(self.package),
+                                    JobStatus.POST_BUILD_FAILED]):
+            if cmd is None:
+                continue
+            try:
+                proc = subprocess.Popen(cmd.split(), bufsize=0,
+                                                     preexec_fn=self.preexec_child,
+                                                     stdout=build_log,
+                                                     stdin=None,
+                                                     stderr=subprocess.STDOUT)
+            except Exception, error:
+                state = 1
+                break
+            state = proc.poll()
+            while not self.do_quit.isSet() and state == None:
+                state = proc.poll()
+                self.do_quit.wait(1)
+            if self.do_quit.isSet():
+                break
+            if state != 0:
+                with self.status_lock:
+                    self.status = failed_status
+                break
+
+        if self.do_quit.isSet():
+            # Kill gently the process
+            RebuilddLog.info("Killing job %s with SIGINT" % self.id)
+            try:
+                os.killpg(os.getpgid(proc.pid), signal.SIGINT)
+            except OSError, error:
+                RebuilddLog.error("Error killing job %s: %s" % (self.id, error))
+
+            # If after 60s it's not dead, KILL HIM
+            counter = 0
+            timemax = RebuilddConfig().get('build', 'kill_timeout')
+            while proc.poll() == None and counter < timemax:
+                time.sleep(1)
+                counter += 1
+            if proc.poll() == None:
+                RebuilddLog.error("Killing job %s timed out, killing with SIGKILL" \
+                           % self.id)
+                os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
+
+            with self.status_lock:
+                self.status = JobStatus.WAIT_LOCKED
+
+            # Reset host
+            self.host = None
+
+            build_log.write("\nBuild job killed on request\n")
+            build_log.close()
+
+            return
+
+        # build is finished
+        with self.status_lock:
+            if state == 0:
+                self.status = JobStatus.BUILD_OK
+
+        self.build_end = sqlobject.DateTimeCol.now()
+
+        build_log.write("******************************************************************************\n")
+        build_log.write("Finished with status %s at %s\n" % (JobStatus.whatis(self.status), self.build_end))
+        build_log.write("Build needed %s\n" % (self.build_end - self.build_start))
+        build_log.close()
+
+
+        # Send event to Rebuildd to inform it that it can
+        # run a brand new job!
+        if self.notify:
+            self.notify.set()
+
+        self.send_build_log()
+
+    def send_build_log(self):
+        """When job is built, send logs by mail"""
+
+        with self.status_lock:
+            if self.status != JobStatus.BUILD_OK and \
+                not self.status in FailedStatus:
+                return False
+
+            if not RebuilddConfig().getboolean('log', 'mail_successful') \
+               and self.status == JobStatus.BUILD_OK:
+                return True
+            elif not RebuilddConfig().getboolean('log', 'mail_failed') \
+                 and self.status in FailedStatus:
+                return True
+
+        if self.status == JobStatus.BUILD_OK:
+            bstatus = "successful"
+        else:
+            bstatus = "failed"
+
+        msg = Message()
+        if self.mailto:
+            msg['To'] = self.mailto
+        else:
+            msg['To'] = RebuilddConfig().get('mail', 'mailto')
+        msg['From'] = RebuilddConfig().get('mail', 'from')
+        msg['Subject'] = RebuilddConfig().get('mail', 'subject_prefix') + \
+                                 " Log for %s build of %s_%s on %s/%s" % \
+                                 (bstatus,
+                                  self.package.name, 
+                                  self.package.version,
+                                  self.dist,
+                                  self.arch)
+        msg['X-Rebuildd-Version'] = __version__
+        msg['X-Rebuildd-Host'] = socket.getfqdn()
+
+      
+        try:
+            with open(self.logfile, "r") as build_log:
+                log = ""
+                for line in build_log.readlines():
+                    log += line
+        except IOError, error:
+            RebuilddLog.error("Unable to open logfile for job %d" % self.id)
+            return False
+
+        msg.set_payload(log)
+
+        try:
+            smtp = smtplib.SMTP()
+            smtp.connect()
+            if self.mailto:
+                smtp.sendmail(RebuilddConfig().get('mail', 'from'),
+                              self.mailto,
+                              msg.as_string())
+            else:
+                smtp.sendmail(RebuilddConfig().get('mail', 'from'),
+                              RebuilddConfig().get('mail', 'mailto'),
+                              msg.as_string())
+        except Exception, error:
+            RebuilddLog.error("Unable to send build log mail for job %d: %s" % (self.id, error))
+
+        return True
+
+    def __str__(self):
+        return "I: Job %s for %s_%s is status %s on %s for %s/%s" % \
+                (self.id, self.package.name, self.package.version, self.host,
+                 JobStatus.whatis(self.status), self.dist, self.arch)
diff -Nru rebuildd-0.4.1/build/lib/rebuildd/JobStatus.py rebuildd-0.4.2/build/lib/rebuildd/JobStatus.py
--- rebuildd-0.4.1/build/lib/rebuildd/JobStatus.py	1970-01-01 01:00:00.000000000 +0100
+++ rebuildd-0.4.2/build/lib/rebuildd/JobStatus.py	2007-11-19 20:47:35.000000000 +0100
@@ -0,0 +1,35 @@
+# rebuildd - Debian packages rebuild tool 
+# 
+# (c) 2007 - Julien Danjou <acid@debian.org> 
+# 
+#   This software 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; version 2 dated June, 1991. 
+# 
+#   This software is distributed in the hope that it will be useful, 
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of 
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
+#   GNU General Public License for more details. 
+# 
+#   You should have received a copy of the GNU General Public License 
+#   along with this software; if not, write to the Free Software 
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA 
+# 
+
+from Enumeration import Enumeration
+
+JobStatus = Enumeration([ ("UNKNOWN", 0), 
+                          ("WAIT", 100), 
+                          ("WAIT_LOCKED", 150),
+                          ("BUILDING", 200), 
+                          ("SOURCE_FAILED", 250), 
+                          ("BUILD_FAILED", 300), 
+                          ("POST_BUILD_FAILED", 350), 
+                          ("CANCELED", 800), 
+                          ("GIVEUP", 850),
+                          ("FAILED", 900),
+                          ("BUILD_OK", 1000) ])
+
+FailedStatus = (JobStatus.SOURCE_FAILED,
+                JobStatus.BUILD_FAILED,
+                JobStatus.POST_BUILD_FAILED)
diff -Nru rebuildd-0.4.1/build/lib/rebuildd/Package.py rebuildd-0.4.2/build/lib/rebuildd/Package.py
--- rebuildd-0.4.1/build/lib/rebuildd/Package.py	1970-01-01 01:00:00.000000000 +0100
+++ rebuildd-0.4.2/build/lib/rebuildd/Package.py	2007-12-03 11:47:42.000000000 +0100
@@ -0,0 +1,36 @@
+# rebuildd - Debian packages rebuild tool 
+# 
+# (c) 2007 - Julien Danjou <acid@debian.org> 
+# 
+#   This software 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; version 2 dated June, 1991. 
+# 
+#   This software is distributed in the hope that it will be useful, 
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of 
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
+#   GNU General Public License for more details. 
+# 
+#   You should have received a copy of the GNU General Public License 
+#   along with this software; if not, write to the Free Software 
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA 
+# 
+
+import sqlobject
+
+# apt warning make me crazzzyyy
+import warnings
+warnings.filterwarnings("ignore", module='apt')
+del warnings
+import apt
+
+class Package(sqlobject.SQLObject): 
+    """Class implemeting a Debian package""" 
+ 
+    name = sqlobject.StringCol() 
+    version = sqlobject.StringCol(default=None) 
+    priority = sqlobject.StringCol(default=None)
+
+    @staticmethod
+    def VersionCompare(a, b):
+        return apt.VersionCompare(a.version, b.version)
diff -Nru rebuildd-0.4.1/build/lib/rebuildd/RebuilddConfig.py rebuildd-0.4.2/build/lib/rebuildd/RebuilddConfig.py
--- rebuildd-0.4.1/build/lib/rebuildd/RebuilddConfig.py	1970-01-01 01:00:00.000000000 +0100
+++ rebuildd-0.4.2/build/lib/rebuildd/RebuilddConfig.py	2008-06-13 20:07:00.000000000 +0200
@@ -0,0 +1,114 @@
+# rebuildd - Debian packages rebuild tool
+#
+# (c) 2007 - Julien Danjou <acid@debian.org>
+#
+#   This software 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; version 2 dated June, 1991.
+#
+#   This software is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this software; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+
+import ConfigParser
+import os
+
+class RebuilddConfig(object, ConfigParser.ConfigParser):
+    """Main configuration singleton"""
+
+    config_file = "/etc/rebuildd/rebuilddrc"
+    _instance = None
+
+    def __new__(cls, *args, **kwargs):
+        if cls._instance is None:
+            cls._instance = object.__new__(cls)
+            cls._instance.init(*args, **kwargs)
+        return cls._instance
+
+    def init(self, dontparse=False):
+        ConfigParser.ConfigParser.__init__(self)
+        # add default sections
+        self.add_section('build')
+        self.add_section('mail')
+        self.add_section('telnet')
+        self.add_section('http')
+        self.add_section('log')
+
+        # add default values
+        self.set('build', 'check_every', '300')
+        self.set('build', 'max_threads', '2')
+        self.set('build', 'max_jobs', '5')
+        self.set('build', 'kill_timeout', '90')
+        self.set('build', 'source_cmd', 'apt-get -q --download-only -t ${d} source ${p}=${v}')
+        self.set('build', 'build_cmd', 'pbuilder build --basetgz /var/cache/pbuilder/${d}-${a}.tgz ${p}_${v}.dsc')
+        self.set('build', 'post_build_cmd', '')
+        self.set('build', 'dists', 'etch lenny sid')
+        self.set('build', 'work_dir', '/var/cache/rebuildd/build')
+        self.set('build', 'database_uri', 'sqlite:///var/lib/rebuildd/rebuildd.db')
+        self.set('build', 'build_more_recent', '1')
+        self.set('build', 'more_archs', 'any')
+
+        self.set('mail', 'from', 'rebuildd@localhost')
+        self.set('mail', 'mailto', 'rebuildd@localhost')
+        self.set('mail', 'subject_prefix', '[rebuildd]')
+
+        self.set('telnet', 'port', '9999')
+        self.set('telnet', 'ip', '127.0.0.1')
+        self.set('telnet', 'prompt', 'rebuildd@localhost->')
+        self.set('telnet', 'motd', 'Connected on rebuildd on localhost')
+
+        self.set('http', 'port', '9998')
+        self.set('http', 'ip', '0.0.0.0')
+        # This is dedicated to MadCoder
+        self.set('http', 'log_lines_nb', '30')
+        self.set('http', 'templates_dir', '/usr/share/rebuildd/templates')
+        self.set('http', 'cache', '1')
+        self.set('http', 'logfile', '/var/log/rebuildd/httpd.log')
+
+        self.set('log', 'file', "/var/log/rebuildd/rebuildd.log")
+        self.set('log', 'time_format', "%d-%m-%Y %H:%M:%S")
+        self.set('log', 'logs_dir', "/var/log/rebuildd/build_logs")
+        self.set('log', 'mail_failed', '1')
+        self.set('log', 'mail_successful', '0')
+
+        self.arch = []
+        parch = os.popen("dpkg --print-architecture")
+        self.arch.append(parch.readline().strip())
+        parch.close()
+
+        if not dontparse:
+            self.reload()
+
+        for a in self.get('build', 'more_archs').split(' '):
+            self.arch.append(a)
+
+    def reload(self):
+        """Reload configuration file"""
+
+        return self.read(self.config_file)
+
+    def dump(self):
+        """Dump running configuration"""
+
+        conf = ""
+        for section in self.sections():
+            conf += "[" + section + "]\n"
+            for item, value in self.items(section):
+                conf += "%s = %s\n" % (item, value)
+            conf += "\n"
+        return conf
+
+    def save(self):
+        """Save configuration file"""
+
+        try:
+            self.write(file(self.config_file, 'w'))
+        except Exception, error:
+            return False
+        return True
diff -Nru rebuildd-0.4.1/build/lib/rebuildd/RebuilddHTTPServer.py rebuildd-0.4.2/build/lib/rebuildd/RebuilddHTTPServer.py
--- rebuildd-0.4.1/build/lib/rebuildd/RebuilddHTTPServer.py	1970-01-01 01:00:00.000000000 +0100
+++ rebuildd-0.4.2/build/lib/rebuildd/RebuilddHTTPServer.py	2009-07-27 15:51:07.000000000 +0200
@@ -0,0 +1,188 @@
+# rebuildd - Debian packages rebuild tool
+#
+# (c) 2007 - Julien Danjou <acid@debian.org>
+#
+#   This software 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; version 2 dated June, 1991.
+#
+#   This software is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this software; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+
+from __future__ import with_statement
+
+from RebuilddConfig import RebuilddConfig
+from Rebuildd import Rebuildd
+from Package import Package
+from Job import Job
+from JobStatus import JobStatus
+from JobStatus import FailedStatus
+
+import tempfile, socket, sqlobject
+import web
+import gdchart
+
+render = web.template.render(RebuilddConfig().get('http', 'templates_dir'), \
+         cache=RebuilddConfig().getboolean('http', 'cache'))
+
+class RequestIndex:
+
+    def GET(self):
+        return render.base(page=render.index(), \
+                hostname=socket.gethostname(), \
+                archs=RebuilddConfig().arch, \
+                dists=RebuilddConfig().get('build', 'dists').split(' '))
+
+class RequestPackage:
+
+    def GET(self, name=None, version=None):
+        jobs = []
+
+        if version:
+            pkg = Package.selectBy(name=name, version=version)[0]
+            title = "%s %s" % (name, version)
+            package = "%s/%s" % (name, version)
+        else:
+            pkg = Package.selectBy(name=name)[0]
+            title = package = name
+
+        jobs.extend(Job.selectBy(package=pkg))
+        return render.base(page=render.tab(jobs=jobs), \
+                hostname=socket.gethostname(), \
+                title=title, \
+                package=package, \
+                archs=RebuilddConfig().arch, \
+                dists=RebuilddConfig().get('build', 'dists').split(' '))
+
+class RequestArch:
+
+    def GET(self, dist, arch=None):
+        jobs = []
+        jobs.extend(Job.select(sqlobject.AND(Job.q.arch == arch, Job.q.dist == dist),
+            orderBy=sqlobject.DESC(Job.q.creation_date))[:10])
+        return render.base(page=render.tab(jobs=jobs), \
+                arch=arch, \
+                dist=dist, \
+                title="%s/%s" % (dist, arch), \
+                hostname=socket.gethostname(), \
+                archs=RebuilddConfig().arch, \
+                dists=RebuilddConfig().get('build', 'dists').split(' '))
+
+class RequestJob:
+
+    def GET(self, jobid=None):
+        job = Job.selectBy(id=jobid)[0]
+
+        try:
+            with open(job.logfile, "r") as build_logfile:
+                build_log = build_logfile.read()
+        except IOError, error:
+            build_log = "No build log available"
+
+        return render.base(page=render.job(job=job, build_log=build_log), \
+                hostname=socket.gethostname(), \
+                title="job %s" % job.id, \
+                archs=RebuilddConfig().arch, \
+                dists=RebuilddConfig().get('build', 'dists').split(' '))
+
+class RequestGraph:
+
+    GET = web.autodelegate("GET_")
+
+    def graph_init(self):
+        web.header("Content-Type","image/png") 
+        graph = gdchart.Bar3D()
+        graph.width = 300
+        graph.height = 300
+        graph.ytitle = "Jobs"
+        graph.xtitle = "Build status"
+        graph.ext_color = [ "yellow", "orange", "red", "green"]
+        graph.bg_color = "white"
+        graph.setLabels(["WAIT", "BUILDING", "FAILED", "OK"])
+
+        return graph
+
+    def compute_stats(self, jobs):
+        jw = 0
+        jb = 0
+        jf = 0
+        jo = 0
+        for job in jobs:
+            if job.status == JobStatus.WAIT or \
+               job.status == JobStatus.WAIT_LOCKED:
+                jw += 1
+            elif job.status == JobStatus.BUILDING:
+                jb += 1
+            elif job.status in FailedStatus:
+                jf += 1
+            elif job.status == JobStatus.BUILD_OK:
+                jo += 1
+
+        return (jw, jb, jf, jo)
+
+    def GET_buildstats(self, distarch=None):
+        graph = self.graph_init()
+        if distarch == "/":
+            graph.title = "Build status"
+            jobs = Job.selectBy()
+        else:
+            dindex = distarch.rindex("/")
+            graph.title = "Build status for %s" % distarch[1:]
+            jobs = Job.selectBy(arch=distarch[dindex+1:], dist=distarch[1:dindex])
+
+        graph.setData(self.compute_stats(jobs))
+        tmp = tempfile.TemporaryFile()
+        graph.draw(tmp)
+        tmp.seek(0)
+        return tmp.read()
+
+    def GET_package(self, package=None):
+        graph = self.graph_init()
+        if package == "/":
+            graph.title = "Build status"
+            jobs = Job.selectBy()
+        else:
+            dindex = package.rindex("/")
+            graph.title = "Build status for %s" % package[1:]
+            pkg = Package.selectBy(version=package[dindex+1:], name=package[1:dindex])[0]
+            jobs = Job.selectBy(package=pkg)
+
+        graph.setData(self.compute_stats(jobs))
+        tmp = tempfile.TemporaryFile()
+        graph.draw(tmp)
+        tmp.seek(0)
+        return tmp.read()
+
+class RebuilddHTTPServer:
+    """Main HTTP server"""
+
+    urls = (
+            '/', 'RequestIndex',
+            '/dist/(.*)/arch/(.*)', 'RequestArch',
+            '/job/(.*)', 'RequestJob',
+            '/package/(.*)/(.*)', 'RequestPackage',
+            '/package/(.*)', 'RequestPackage',
+            '/graph/(.*)', 'RequestGraph',
+            )
+
+    def __init__(self):
+        Rebuildd()
+
+    def start(self):
+
+        """Run main HTTP server thread"""
+
+        web.webapi.internalerror = web.debugerror
+
+        import sys; sys.argv.append(RebuilddConfig().get('http', 'ip') + ":" + RebuilddConfig().get('http', 'port'))
+
+	app = web.application(self.urls, globals())
+	app.run()
+
diff -Nru rebuildd-0.4.1/build/lib/rebuildd/RebuilddLog.py rebuildd-0.4.2/build/lib/rebuildd/RebuilddLog.py
--- rebuildd-0.4.1/build/lib/rebuildd/RebuilddLog.py	1970-01-01 01:00:00.000000000 +0100
+++ rebuildd-0.4.2/build/lib/rebuildd/RebuilddLog.py	2007-11-19 20:47:35.000000000 +0100
@@ -0,0 +1,58 @@
+# rebuildd - Debian packages rebuild tool
+#
+# (c) 2007 - Julien Danjou <acid@debian.org>
+#
+#   This software 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; version 2 dated June, 1991.
+#
+#   This software is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this software; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+
+import logging
+from RebuilddConfig import RebuilddConfig
+
+class RebuilddLog(object):
+    """Singleton used for logging"""
+
+    _instance = None
+
+    def __new__(cls):
+        if cls._instance is None:
+            cls._instance = object.__new__(cls)
+            cls._instance.init()
+        return cls._instance
+
+    def init(self):
+        cfg = RebuilddConfig()
+        logging.basicConfig(level=logging.DEBUG,
+                            format='%(asctime)s %(levelname)s %(message)s',
+                            filename=cfg.get('log', 'file'),
+                            datefmt=cfg.get('log', 'time_format'),
+                            filemode='a')
+
+    @classmethod
+    def info(self, str):
+        """Log a string with info priority"""
+
+        logging.info(str)
+
+    @classmethod
+    def warn(self, str):
+        """Log a string with warn priority"""
+        
+        logging.warning(str)
+
+    @classmethod
+    def error(self, str):
+        """Log a string with error priority"""
+        
+        logging.error(str)
+
diff -Nru rebuildd-0.4.1/build/lib/rebuildd/RebuilddNetworkClient.py rebuildd-0.4.2/build/lib/rebuildd/RebuilddNetworkClient.py
--- rebuildd-0.4.1/build/lib/rebuildd/RebuilddNetworkClient.py	1970-01-01 01:00:00.000000000 +0100
+++ rebuildd-0.4.2/build/lib/rebuildd/RebuilddNetworkClient.py	2007-12-03 11:47:42.000000000 +0100
@@ -0,0 +1,212 @@
+# rebuildd - Debian packages rebuild tool
+#
+# (c) 2007 - Julien Danjou <acid@debian.org>
+#
+#   This software 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; version 2 dated June, 1991.
+#
+#   This software is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this software; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+
+from __future__ import with_statement
+
+from RebuilddConfig import RebuilddConfig
+import threading, socket
+
+__version__ = "$Rev$"
+
+class RebuilddNetworkClient(threading.Thread):
+    """Network client used for each connection"""
+
+    def __init__(self, socket, rebuildd):
+        threading.Thread.__init__(self)
+        self.rebuildd = rebuildd
+        self.socket = socket
+
+    def run(self):
+        """Run client thread"""
+
+        self.socket.settimeout(1)
+        self.socket.send(RebuilddConfig().get('telnet', 'motd') + "\n")
+        prompt = RebuilddConfig().get('telnet', 'prompt') + " "
+        line = ""
+        has_timeout = False
+        while line != "exit" and not self.rebuildd.do_quit.isSet():
+            if not has_timeout:
+                try:
+                    self.socket.send(self.exec_cmd(line))
+                    self.socket.send(prompt)
+                except Exception:
+                    break
+            try:
+                line = ""
+                line = self.socket.recv(512).strip()
+                has_timeout = False
+            except socket.timeout:
+                has_timeout = True
+
+        self.socket.close()
+
+    def exec_cmd(self, cmd):
+        """Execute a command asked by a client"""
+
+        # Return empty if empty
+        if cmd == "":
+            return ""
+
+        # Split words
+        op = cmd.split(' ')
+        cmd_fct = op[0]
+        if not cmd_fct.isalnum():
+            return ""
+        op.remove(op[0])
+        try:
+            return getattr(self, "exec_cmd_" + cmd_fct)(*tuple(op))
+        except Exception, error:
+            return "E: command error: %s\n" % error
+
+    def exec_cmd_help(self, *args):
+        """Show help"""
+
+        help = ""
+        for attr in dir(RebuilddNetworkClient):
+            if attr.startswith("exec_cmd_"):
+                help += "%s -- %s\n" \
+                        % (attr[9:].replace("_", " "),
+                           getattr(RebuilddNetworkClient, attr).__doc__)
+
+        return help
+        
+    def exec_cmd_config(self, *args):
+        """Manipulate configuration file"""
+
+        if len(args) < 1:
+            return "E: usage: config [reload|dump|save]\n"
+
+        if args[0] == "reload":
+            if RebuilddConfig().reload():
+                return "I: config reloaded\n"
+            return "E: config not reloded\n"
+
+        if args[0] == "dump":
+            return RebuilddConfig().dump()
+
+        if args[0] == "save":
+            if RebuilddConfig().save():
+                return "I: config saved\n"
+            return "E: config not saved\n"
+
+        return "E: usage: config [reload|dump|save]\n"
+
+    def exec_cmd_status(self, *args):
+        """Show current jobs status"""
+        return self.rebuildd.dump_jobs()
+
+    def exec_cmd_version(self, *args):
+        """Show version"""
+        return __version__ + "\n"
+
+    def exec_cmd_job(self, *args):
+        """Manipulate jobs"""
+
+        if len(args) > 0 and args[0] == "add":
+            return self.exec_cmd_job_add(*args)
+
+        if len(args) > 0 and args[0] == "cancel":
+            return self.exec_cmd_job_cancel(*args)
+
+        if len(args) > 0 and args[0] == "start":
+            return self.exec_cmd_job_start(*args)
+
+        if len(args) > 0 and args[0] == "reload":
+            return self.exec_cmd_job_reload(*args)
+
+        if len(args) > 0 and args[0] == "status":
+            return self.exec_cmd_job_status(*args)
+
+        return "E: usage: job <command> [args]\n"
+
+    def exec_cmd_job_add(self, *args):
+        """Add job"""
+
+        ret = False
+        if len(args) < 4:
+            return "E: usage: job add <name> <ver> <priority> <dist> [arch] [mailto]\n"
+
+        if len(args) == 5:
+            ret = self.rebuildd.add_job(name=args[1],
+                                        version=args[2],
+                                        priority=args[3],
+                                        dist=args[4])
+
+        if len(args) == 6:
+            ret = self.rebuildd.add_job(name=args[1],
+                                        version=args[2],
+                                        priority=args[3],
+                                        dist=args[4],
+                                        arch=args[5])
+
+        if len(args) == 7:
+            ret = self.rebuildd.add_job(name=args[1], 
+                                        version=args[2],
+                                        priority=args[3],
+                                        dist=args[4], 
+                                        arch=args[5],
+                                        mailto=args[6])
+        
+        if ret:
+            return "I: job added\n"
+        return "E: error adding job\n"
+
+    def exec_cmd_job_cancel(self, *args):
+        """Cancel job"""
+
+        if len(args) < 2:
+            return "E: usage: job cancel <id>\n"
+        if self.rebuildd.cancel_job(int(args[1])):
+            return "I: job canceled\n"
+        return "E: unknown job\n"
+
+    def exec_cmd_job_start(self, *args):
+        """Start jobs"""
+
+        if len(args) == 2:
+            return "I: %s jobs started\n" \
+                    % self.rebuildd.start_jobs(int(args[1]))
+        return "I: %s jobs started\n" \
+                % self.rebuildd.start_jobs()
+
+    def exec_cmd_job_reload(self, *args):
+        """Load new jobs"""
+
+        return "I: %s new jobs added\n" % self.rebuildd.get_new_jobs()
+
+    def exec_cmd_job_status(self, *args):
+        """Dump job status"""
+
+        if len(args) < 2 or len(args) > 5:
+            return "E: usage: job status <name> <ver> <dist> [arch]\n"
+        elif len(args) == 2:
+            jobs = self.rebuildd.get_jobs(name=args[1])
+        elif len(args) == 3:
+            jobs = self.rebuildd.get_jobs(name=args[1],
+                                          version=args[2])
+        elif len(args) == 4:
+            jobs = self.rebuildd.get_jobs(name=args[1],
+                                          version=args[2],
+                                          dist=args[3])
+        elif len(args) == 5:
+            jobs = self.rebuildd.get_jobs(name=args[1],
+                                          version=args[2],
+                                          dist=args[3],
+                                          arch=args[4])
+
+        return self.rebuildd.dump_jobs(jobs)
diff -Nru rebuildd-0.4.1/build/lib/rebuildd/RebuilddNetworkServer.py rebuildd-0.4.2/build/lib/rebuildd/RebuilddNetworkServer.py
--- rebuildd-0.4.1/build/lib/rebuildd/RebuilddNetworkServer.py	1970-01-01 01:00:00.000000000 +0100
+++ rebuildd-0.4.2/build/lib/rebuildd/RebuilddNetworkServer.py	2007-11-19 20:47:35.000000000 +0100
@@ -0,0 +1,50 @@
+# rebuildd - Debian packages rebuild tool
+#
+# (c) 2007 - Julien Danjou <acid@debian.org>
+#
+#   This software 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; version 2 dated June, 1991.
+#
+#   This software is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this software; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+
+import threading, socket
+from RebuilddConfig import RebuilddConfig
+from RebuilddNetworkClient import RebuilddNetworkClient
+
+class RebuilddNetworkServer(threading.Thread):
+    """Main network server listening for connection"""
+
+    def __init__(self, rebuildd):
+        threading.Thread.__init__(self)
+        self.rebuildd = rebuildd
+
+    def run(self):
+        """Run main network server thread"""
+
+        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        self.socket.settimeout(1)
+        self.socket.bind((RebuilddConfig().get('telnet', 'ip'),
+                          RebuilddConfig().getint('telnet', 'port')))
+        self.socket.listen(2)
+        while not self.rebuildd.do_quit.isSet():
+            try:
+                (client_socket, client_info) = self.socket.accept()
+                if client_socket:
+                    interface = RebuilddNetworkClient(client_socket,
+                                                      self.rebuildd)
+                    interface.setDaemon(True)
+                    interface.start()
+            except socket.timeout:
+                pass
+
+        self.socket.close()
diff -Nru rebuildd-0.4.1/build/lib/rebuildd/Rebuildd.py rebuildd-0.4.2/build/lib/rebuildd/Rebuildd.py
--- rebuildd-0.4.1/build/lib/rebuildd/Rebuildd.py	1970-01-01 01:00:00.000000000 +0100
+++ rebuildd-0.4.2/build/lib/rebuildd/Rebuildd.py	2007-12-03 11:47:42.000000000 +0100
@@ -0,0 +1,379 @@
+# rebuildd - Debian packages rebuild tool
+#
+# (c) 2007 - Julien Danjou <acid@debian.org>
+#
+#   This software 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; version 2 dated June, 1991.
+#
+#   This software is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this software; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+
+"""rebuildd - Debian packages rebuild tool"""
+
+from __future__ import with_statement
+
+from Enumeration import Enumeration
+from Distribution import Distribution
+from Dists import Dists
+from RebuilddLog import RebuilddLog
+from RebuilddConfig import RebuilddConfig
+from RebuilddNetworkServer import RebuilddNetworkServer
+from Package import Package
+from Job import Job
+from JobStatus import JobStatus
+from JobStatus import FailedStatus
+import threading, os, time, sys, signal, socket
+import sqlobject
+
+__version__ = "$Rev$"
+
+class Rebuildd(object):
+    jobs = []
+    _instance = None 
+         
+    def __new__(cls):  
+        if cls._instance is None:  
+           cls._instance = object.__new__(cls)  
+           cls._instance.init()
+        return cls._instance  
+
+    def init(self):
+        self.cfg = RebuilddConfig()
+
+        # Init log system
+        RebuilddLog()
+
+        sqlobject.sqlhub.processConnection = \
+            sqlobject.connectionForURI(self.cfg.get('build', 'database_uri')) 
+
+        # Create distributions
+        for dist in self.cfg.get('build', 'dists').split(' '):
+            for arch in self.cfg.arch:
+                Dists().add_dist(Distribution(dist, arch))
+
+        self.do_quit = threading.Event()
+        self.jobs_locker = threading.Lock()
+        self.job_finished = threading.Event()
+
+    def daemon(self):
+        RebuilddLog.info("Starting rebuildd %s" % __version__)
+        self.daemonize()
+
+        # Run the network server thread
+        RebuilddLog.info("Launching network server")
+        self.netserv = RebuilddNetworkServer(self)
+        self.netserv.setDaemon(True)
+        self.netserv.start()
+
+        # Run main loop
+        RebuilddLog.info("Running main loop")
+        self.loop()
+
+        # On exit
+        RebuilddLog.info("Cleaning finished and canceled jobs")
+        self.clean_jobs()
+        RebuilddLog.info("Stopping all jobs")
+        self.stop_all_jobs()
+        RebuilddLog.info("Releasing wait-locked jobs")
+        self.release_jobs()
+        self.netserv.join(10)
+        RebuilddLog.info("Exiting rebuildd")
+
+    def daemonize(self):
+        """Do daemon stuff"""
+
+        signal.signal(signal.SIGTERM, self.handle_sigterm)
+        signal.signal(signal.SIGINT, self.handle_sigterm)
+
+        try:
+            os.chdir(self.cfg.get('build', 'work_dir'))
+        except Exception, error:
+            print "E: unable to chdir to work_dir: %s" % error
+            sys.exit(1)
+
+        try:
+            sys.stdout = sys.stderr = file(self.cfg.get('log', 'file'), "a")
+        except Exception, error:
+            print "E: unable to open logfile: %s" % error
+
+    def get_job(self, jobid):
+        for job in self.jobs:
+            if job.id == jobid:
+                return job
+
+        return None
+
+    def get_all_jobs(self, **kwargs):
+        jobs = []
+        jobs.extend(Job.selectBy(**kwargs))
+        return jobs
+
+    def get_new_jobs(self):
+        """Feed jobs list with waiting jobs and lock them"""
+
+        max_new = self.cfg.getint('build', 'max_jobs')
+        count_current = len(self.jobs)
+
+        with self.jobs_locker:
+            if count_current >= max_new:
+                return 0
+
+            jobs = []
+            for dist in Dists().dists: 
+                jobs.extend(Job.selectBy(status=JobStatus.WAIT, dist=dist.name, arch=dist.arch)[:max_new])
+
+            count_new = 0
+            for job in jobs:
+                # Look for higher versions ?
+                if self.cfg.getboolean('build', 'build_more_recent'):
+                    packages = Package.selectBy(name=job.package.name)
+                    candidate_packages = []
+                    candidate_packages.extend(packages)
+                    candidate_packages.sort(cmp=Package.VersionCompare)
+                    candidate_packages.reverse()
+                    newjob = None
+
+                    # so there are packages with higher version number
+                    # try to see if there's a job for us
+                    for cpackage in candidate_packages:
+                        candidate_jobs = []
+                        candidate_jobs.extend(Job.selectBy(package=cpackage, dist=job.dist, arch=job.arch))
+                        for cjob in candidate_jobs:
+                            if newjob and newjob != cjob and cjob.status == JobStatus.WAIT:
+                                cjob.status = JobStatus.GIVEUP
+                            elif cjob.status == JobStatus.WAIT:
+                                newjob = cjob
+
+                    job = newjob
+
+                # We have to check because it might have changed
+                # between our first select and the build_more_recent stuffs
+
+                if not job or job.status != JobStatus.WAIT:
+                    continue
+
+                job.status = JobStatus.WAIT_LOCKED
+                job.host = socket.gethostname()
+                self.jobs.append(job)
+                count_new += 1
+                count_current += 1
+
+                if count_current >= max_new:
+                    break
+
+        return count_new
+
+    def count_running_jobs(self):
+        """Count running jobs"""
+
+        count = 0
+        with self.jobs_locker:
+            for job in self.jobs:
+                if job.status == JobStatus.BUILDING and job.isAlive():
+                    count += 1
+
+        return count
+
+    def start_jobs(self, overrun=0):
+        """Start waiting jobs"""
+
+        running_threads = self.count_running_jobs()
+        max_threads = max(overrun, self.cfg.getint('build', 'max_threads'))
+        jobs_started = 0
+
+        with self.jobs_locker:
+            for job in self.jobs:
+                if running_threads >= max_threads:
+                    break
+            
+                with job.status_lock:
+                    if job.status == JobStatus.WAIT_LOCKED and not job.isAlive():
+                        RebuilddLog.info("Starting new thread for job %s" % job.id)
+                        job.notify = self.job_finished
+                        job.setDaemon(True)
+                        job.start()
+                        jobs_started += 1
+                        running_threads = running_threads + 1
+
+        RebuilddLog.info("Running threads: [ build %s/%s ] [ real %s ]" %
+                           (running_threads, max_threads, threading.activeCount()))
+
+        return jobs_started
+
+    def get_jobs(self, name, version=None, dist=None, arch=None):
+        """Dump a job status"""
+        
+        if version:
+            pkgs = Package.selectBy(name=name, version=version)
+        else:
+            pkgs = Package.selectBy(name=name)
+
+        if not pkgs.count():
+            return []
+
+        retjobs = []
+        if dist and arch:
+            for pkg in pkgs:
+                retjobs.extend(Job.selectBy(package=pkg, dist=dist, arch=arch))
+        elif dist:
+            for pkg in pkgs:
+                retjobs.extend(Job.selectBy(package=pkg, dist=dist))
+        elif arch:
+            for pkg in pkgs:
+                retjobs.extend(Job.selectBy(package=pkg, arch=arch))
+        else:
+            for pkg in pkgs:
+                retjobs.extend(Job.selectBy(package=pkg))
+        
+        return retjobs
+
+    def dump_jobs(self, joblist=None):
+        """Dump all jobs status"""
+
+        ret = ""
+
+        if not joblist:
+            joblist = self.jobs
+
+        for job in joblist:
+            ret = "%s%s\n" % (ret, str(job))
+
+        return ret
+
+    def cancel_job(self, jobid):
+        """Cancel a job"""
+
+        with self.jobs_locker:
+            job = self.get_job(jobid)
+            if job != None:
+                if job.isAlive():
+                    job.do_quit.set()
+                    job.join()
+                with job.status_lock:
+                    job.status = JobStatus.CANCELED
+                self.jobs.remove(job)
+                RebuilddLog.info("Canceled job %s for %s_%s on %s/%s for %s" \
+                                % (job.id, job.package.name, job.package.version,
+                                job.dist, job.arch, job.mailto))
+                return True
+
+        return False
+
+    def stop_all_jobs(self):
+        """Stop all running jobs"""
+
+        with self.jobs_locker:
+            for job in self.jobs:
+                if job.status == JobStatus.BUILDING and job.isAlive():
+                    job.do_quit.set()
+                    RebuilddLog.info("Sending stop to job %s" % job.id)
+            for job in self.jobs:
+                if job.isAlive():
+                    RebuilddLog.info("Waiting for job %s to terminate" % job.id)
+                    job.join(60)
+
+        return True
+
+    def add_job(self, name, version, priority, dist, mailto=None, arch=None):
+        """Add a job"""
+
+        if not arch:
+            arch = self.cfg.arch[0]
+
+        if not Dists().get_dist(dist, arch):
+            return False
+
+        pkgs = Package.selectBy(name=name, version=version)
+        if pkgs.count():
+            # If several packages exists, just take the first
+            pkg = pkgs[0]
+        else:
+            # Maybe we found no packages, so create a brand new one!
+            pkg = Package(name=name, version=version, priority=priority)
+
+        jobs_count = Job.selectBy(package=pkg, dist=dist, arch=arch, mailto=mailto, status=JobStatus.WAIT).count()
+        if jobs_count:
+            RebuilddLog.error("Job already existing for %s_%s on %s/%s, don't adding it" \
+                           % (pkg.name, pkg.version, dist, arch))
+            return False
+
+        job = Job(package=pkg, dist=dist, arch=arch)
+        job.status = JobStatus.WAIT
+        job.arch = arch
+        job.mailto = mailto
+
+        RebuilddLog.info("Added job for %s_%s on %s/%s for %s" \
+                      % (name, version, dist, arch, mailto))
+        return True
+
+    def clean_jobs(self):
+        """Clean finished or canceled jobs"""
+
+        with self.jobs_locker:
+            for job in self.jobs:
+                if job.status == JobStatus.BUILD_OK \
+                   or job.status in FailedStatus \
+                   or job.status == JobStatus.CANCELED:
+                    self.jobs.remove(job)
+
+        return True
+
+    def release_jobs(self):
+        """Release jobs"""
+
+        with self.jobs_locker:
+            for job in self.jobs:
+                with job.status_lock:
+                    if job.status == JobStatus.WAIT_LOCKED:
+                        job.status = JobStatus.WAIT
+                        job.host = ""
+
+        return True
+
+    def fix_jobs(self, print_result=True):
+        """If rebuildd crashed, reset jobs to a valid state"""
+
+        jobs = []
+        jobs.extend(Job.selectBy(host=socket.gethostname(), status=JobStatus.WAIT_LOCKED))
+        jobs.extend(Job.selectBy(host=socket.gethostname(), status=JobStatus.BUILDING))
+
+        for job in jobs:
+            if print_result:
+                print "I: Fixing job %s (was %s)" % (job.id, JobStatus.whatis(job.status))
+            job.host = None
+            job.status = JobStatus.WAIT
+            job.build_start = None
+            job.build_end = None
+
+        return True
+
+    def handle_sigterm(self, signum, stack):
+        RebuilddLog.info("Receiving transmission... it's a signal %s capt'ain! EVERYONE OUT!" % signum)
+        self.do_quit.set()
+
+    def loop(self):
+        """Rebuildd main loop"""
+
+        counter = self.cfg.getint('build', 'check_every')
+        while not self.do_quit.isSet():
+            if counter == self.cfg.getint('build', 'check_every') \
+               or self.job_finished.isSet():
+                self.get_new_jobs()
+                # Start jobs
+                self.start_jobs()
+                # Clean finished jobs
+                self.clean_jobs()
+                counter = 0
+                self.job_finished.clear()
+            self.do_quit.wait(1)
+            counter += 1
+
+
diff -Nru rebuildd-0.4.1/build/lib.linux-x86_64-2.6/rebuildd/__init__.py rebuildd-0.4.2/build/lib.linux-x86_64-2.6/rebuildd/__init__.py
--- rebuildd-0.4.1/build/lib.linux-x86_64-2.6/rebuildd/__init__.py	2011-10-05 16:05:33.000000000 +0200
+++ rebuildd-0.4.2/build/lib.linux-x86_64-2.6/rebuildd/__init__.py	2012-07-24 10:59:36.000000000 +0200
@@ -19,8 +19,6 @@
 """rebuildd - Debian packages rebuild tool"""
 
 __author__  = "Julien Danjou <acid@debian.org>"
-__cvsid__   = "$Id$"
-__version__ = "$Rev$"[6:-2]
 __all__ = [
         "Distribution",
         "Enumeration",
diff -Nru rebuildd-0.4.1/build/lib.linux-x86_64-2.6/rebuildd/Job.py rebuildd-0.4.2/build/lib.linux-x86_64-2.6/rebuildd/Job.py
--- rebuildd-0.4.1/build/lib.linux-x86_64-2.6/rebuildd/Job.py	2012-02-13 12:11:47.000000000 +0100
+++ rebuildd-0.4.2/build/lib.linux-x86_64-2.6/rebuildd/Job.py	2012-07-24 10:59:36.000000000 +0200
@@ -20,6 +20,7 @@
 
 import threading, subprocess, smtplib, time, os, signal, socket, select
 import sqlobject
+from subprocess import Popen,PIPE
 from email.Message import Message
 from Dists import Dists
 from JobStatus import JobStatus
@@ -52,8 +53,6 @@
 
         threading.Thread.__init__(self)
         sqlobject.SQLObject.__init__(self, *args, **kwargs)
-        if self.log is None:
-            log = Log(job=self)
 
         self.do_quit = threading.Event()
         self.status_lock = threading.Lock()
@@ -201,7 +200,8 @@
             return False
 
         # Store in database
-        self.log.text = log
+        if self.log:
+            self.log.text = log
 
         with self.status_lock:
             if self.status != JobStatus.BUILD_OK and \
@@ -243,16 +243,15 @@
             smtp = smtplib.SMTP()
             smtp.connect(RebuilddConfig().get('mail', 'smtp_host'),
                          RebuilddConfig().get('mail', 'smtp_port'))
-            if self.mailto:
-                smtp.sendmail(RebuilddConfig().get('mail', 'from'),
-                              self.mailto,
-                              msg.as_string())
-            else:
-                smtp.sendmail(RebuilddConfig().get('mail', 'from'),
-                              RebuilddConfig().get('mail', 'mailto'),
-                              msg.as_string())
+            smtp.sendmail(RebuilddConfig().get('mail', 'from'),
+                          [m.strip() for m in msg['To'].split(",")],
+                          msg.as_string())
         except Exception, error:
-            RebuilddLog.error("Unable to send build log mail for job %d: %s" % (self.id, error))
+            try:
+                process = Popen("sendmail", shell=True, stdin=PIPE)
+                process.communicate(input=msg.as_string())
+            except:
+                RebuilddLog.error("Unable to send build log mail for job %d: %s" % (self.id, error))
 
         return True
 
diff -Nru rebuildd-0.4.1/build/lib.linux-x86_64-2.6/rebuildd/RebuilddConfig.py rebuildd-0.4.2/build/lib.linux-x86_64-2.6/rebuildd/RebuilddConfig.py
--- rebuildd-0.4.1/build/lib.linux-x86_64-2.6/rebuildd/RebuilddConfig.py	2011-10-05 16:05:33.000000000 +0200
+++ rebuildd-0.4.2/build/lib.linux-x86_64-2.6/rebuildd/RebuilddConfig.py	2012-07-24 10:59:36.000000000 +0200
@@ -53,6 +53,7 @@
         self.set('build', 'database_uri', 'sqlite:///var/lib/rebuildd/rebuildd.db')
         self.set('build', 'build_more_recent', '1')
         self.set('build', 'more_archs', 'any')
+        self.set('build', 'no_system_arch', '0')
 
         self.set('mail', 'from', 'rebuildd@localhost')
         self.set('mail', 'mailto', 'rebuildd@localhost')
@@ -74,19 +75,20 @@
         self.set('http', 'logfile', '/var/log/rebuildd/httpd.log')
 
         self.set('log', 'file', "/var/log/rebuildd/rebuildd.log")
-        self.set('log', 'time_format', "%d-%m-%Y %H:%M:%S")
+        self.set('log', 'time_format', "%Y-%m-%d %H:%M:%S")
         self.set('log', 'logs_dir', "/var/log/rebuildd/build_logs")
         self.set('log', 'mail_failed', '1')
         self.set('log', 'mail_successful', '0')
 
-        self.arch = []
-        parch = os.popen("dpkg --print-architecture")
-        self.arch.append(parch.readline().strip())
-        parch.close()
-
         if not dontparse:
             self.reload()
 
+        self.arch = []
+        if self.getint('build', 'no_system_arch') == 0:
+            parch = os.popen("dpkg --print-architecture")
+            self.arch.append(parch.readline().strip())
+            parch.close()
+
         for a in self.get('build', 'more_archs').split(' '):
             self.arch.append(a)
 
diff -Nru rebuildd-0.4.1/build/lib.linux-x86_64-2.6/rebuildd/RebuilddNetworkClient.py rebuildd-0.4.2/build/lib.linux-x86_64-2.6/rebuildd/RebuilddNetworkClient.py
--- rebuildd-0.4.1/build/lib.linux-x86_64-2.6/rebuildd/RebuilddNetworkClient.py	2011-10-05 16:05:33.000000000 +0200
+++ rebuildd-0.4.2/build/lib.linux-x86_64-2.6/rebuildd/RebuilddNetworkClient.py	2012-07-24 10:59:36.000000000 +0200
@@ -135,6 +135,9 @@
         if len(args) > 0 and args[0] == "status":
             return self.exec_cmd_job_status(*args)
 
+        if len(args) > 0 and args[0] == "requeue":
+            return self.exec_cmd_job_requeue(*args)
+
         return "E: usage: job <command> [args]\n"
 
     def exec_cmd_job_add(self, *args):
@@ -169,12 +172,21 @@
             return "I: job added\n"
         return "E: error adding job\n"
 
+    def exec_cmd_job_requeue(self, *args):
+        """Requeue job"""
+
+        if len(args) != 2:
+            return "E: usage: job requeue <job_id>\n"
+
+        if self.rebuildd.requeue_job(job_id=int(args[1])):
+            return "I: job requeued\n"
+        return "E: error requeuing the job\n"
 
     def exec_cmd_job_deps(self, *args):
         """Add dependency"""
 
         ret = False
-        if len(args) < 2:
+        if len(args) < 3:
             return "E: usage: job deps <job_id> <dependency_job_id> [dependency_job_id] [...]\n"
 
 	ret = self.rebuildd.add_deps(job_id=args[1],
diff -Nru rebuildd-0.4.1/build/lib.linux-x86_64-2.6/rebuildd/Rebuildd.py rebuildd-0.4.2/build/lib.linux-x86_64-2.6/rebuildd/Rebuildd.py
--- rebuildd-0.4.1/build/lib.linux-x86_64-2.6/rebuildd/Rebuildd.py	2012-02-13 12:11:47.000000000 +0100
+++ rebuildd-0.4.2/build/lib.linux-x86_64-2.6/rebuildd/Rebuildd.py	2012-07-24 10:59:36.000000000 +0200
@@ -293,7 +293,7 @@
             arch = self.cfg.arch[0]
 
         if not Dists().get_dist(dist, arch):
-            RebuilddLog.error("Couldn't find dist/arch in the config file for %s_%s on %s/%s, don't adding it" \
+            RebuilddLog.error("Couldn't find dist/arch in the config file for %s_%s on %s/%s, not adding it" \
                            % (name, version, dist, arch))
             return False
 
@@ -307,7 +307,7 @@
 
         jobs_count = Job.selectBy(package=pkg, dist=dist, arch=arch, mailto=mailto, status=JobStatus.WAIT).count()
         if jobs_count:
-            RebuilddLog.error("Job already existing for %s_%s on %s/%s, don't adding it" \
+            RebuilddLog.error("Job already existing for %s_%s on %s/%s, not adding it" \
                            % (pkg.name, pkg.version, dist, arch))
             return False
 
@@ -322,6 +322,20 @@
                       % (name, version, dist, arch, mailto))
         return True
 
+    def requeue_job(self, job_id):
+        """Requeue a failed job"""
+
+        if Job.selectBy(id=job_id).count() == 0:
+            RebuilddLog.error("There is no job related to %s that is in the job list" % job_id)
+            return False
+        job = Job.selectBy(id=job_id)[0]
+
+        if job.status in FailedStatus:
+            job.status = JobStatus.WAIT
+            job.host = ""
+
+        return True
+
     def add_deps(self, job_id, dependency_ids):
 
         if Job.selectBy(id=job_id).count() == 0:
diff -Nru rebuildd-0.4.1/build/lib.linux-x86_64-2.7/rebuildd/__init__.py rebuildd-0.4.2/build/lib.linux-x86_64-2.7/rebuildd/__init__.py
--- rebuildd-0.4.1/build/lib.linux-x86_64-2.7/rebuildd/__init__.py	2011-10-05 16:05:33.000000000 +0200
+++ rebuildd-0.4.2/build/lib.linux-x86_64-2.7/rebuildd/__init__.py	2012-07-24 10:59:36.000000000 +0200
@@ -19,8 +19,6 @@
 """rebuildd - Debian packages rebuild tool"""
 
 __author__  = "Julien Danjou <acid@debian.org>"
-__cvsid__   = "$Id$"
-__version__ = "$Rev$"[6:-2]
 __all__ = [
         "Distribution",
         "Enumeration",
diff -Nru rebuildd-0.4.1/build/lib.linux-x86_64-2.7/rebuildd/Job.py rebuildd-0.4.2/build/lib.linux-x86_64-2.7/rebuildd/Job.py
--- rebuildd-0.4.1/build/lib.linux-x86_64-2.7/rebuildd/Job.py	2012-02-13 12:11:47.000000000 +0100
+++ rebuildd-0.4.2/build/lib.linux-x86_64-2.7/rebuildd/Job.py	2012-07-24 10:59:36.000000000 +0200
@@ -20,6 +20,7 @@
 
 import threading, subprocess, smtplib, time, os, signal, socket, select
 import sqlobject
+from subprocess import Popen,PIPE
 from email.Message import Message
 from Dists import Dists
 from JobStatus import JobStatus
@@ -52,8 +53,6 @@
 
         threading.Thread.__init__(self)
         sqlobject.SQLObject.__init__(self, *args, **kwargs)
-        if self.log is None:
-            log = Log(job=self)
 
         self.do_quit = threading.Event()
         self.status_lock = threading.Lock()
@@ -201,7 +200,8 @@
             return False
 
         # Store in database
-        self.log.text = log
+        if self.log:
+            self.log.text = log
 
         with self.status_lock:
             if self.status != JobStatus.BUILD_OK and \
@@ -243,16 +243,15 @@
             smtp = smtplib.SMTP()
             smtp.connect(RebuilddConfig().get('mail', 'smtp_host'),
                          RebuilddConfig().get('mail', 'smtp_port'))
-            if self.mailto:
-                smtp.sendmail(RebuilddConfig().get('mail', 'from'),
-                              self.mailto,
-                              msg.as_string())
-            else:
-                smtp.sendmail(RebuilddConfig().get('mail', 'from'),
-                              RebuilddConfig().get('mail', 'mailto'),
-                              msg.as_string())
+            smtp.sendmail(RebuilddConfig().get('mail', 'from'),
+                          [m.strip() for m in msg['To'].split(",")],
+                          msg.as_string())
         except Exception, error:
-            RebuilddLog.error("Unable to send build log mail for job %d: %s" % (self.id, error))
+            try:
+                process = Popen("sendmail", shell=True, stdin=PIPE)
+                process.communicate(input=msg.as_string())
+            except:
+                RebuilddLog.error("Unable to send build log mail for job %d: %s" % (self.id, error))
 
         return True
 
diff -Nru rebuildd-0.4.1/build/lib.linux-x86_64-2.7/rebuildd/RebuilddConfig.py rebuildd-0.4.2/build/lib.linux-x86_64-2.7/rebuildd/RebuilddConfig.py
--- rebuildd-0.4.1/build/lib.linux-x86_64-2.7/rebuildd/RebuilddConfig.py	2011-10-05 16:05:33.000000000 +0200
+++ rebuildd-0.4.2/build/lib.linux-x86_64-2.7/rebuildd/RebuilddConfig.py	2012-07-24 10:59:36.000000000 +0200
@@ -53,6 +53,7 @@
         self.set('build', 'database_uri', 'sqlite:///var/lib/rebuildd/rebuildd.db')
         self.set('build', 'build_more_recent', '1')
         self.set('build', 'more_archs', 'any')
+        self.set('build', 'no_system_arch', '0')
 
         self.set('mail', 'from', 'rebuildd@localhost')
         self.set('mail', 'mailto', 'rebuildd@localhost')
@@ -74,19 +75,20 @@
         self.set('http', 'logfile', '/var/log/rebuildd/httpd.log')
 
         self.set('log', 'file', "/var/log/rebuildd/rebuildd.log")
-        self.set('log', 'time_format', "%d-%m-%Y %H:%M:%S")
+        self.set('log', 'time_format', "%Y-%m-%d %H:%M:%S")
         self.set('log', 'logs_dir', "/var/log/rebuildd/build_logs")
         self.set('log', 'mail_failed', '1')
         self.set('log', 'mail_successful', '0')
 
-        self.arch = []
-        parch = os.popen("dpkg --print-architecture")
-        self.arch.append(parch.readline().strip())
-        parch.close()
-
         if not dontparse:
             self.reload()
 
+        self.arch = []
+        if self.getint('build', 'no_system_arch') == 0:
+            parch = os.popen("dpkg --print-architecture")
+            self.arch.append(parch.readline().strip())
+            parch.close()
+
         for a in self.get('build', 'more_archs').split(' '):
             self.arch.append(a)
 
diff -Nru rebuildd-0.4.1/build/lib.linux-x86_64-2.7/rebuildd/RebuilddNetworkClient.py rebuildd-0.4.2/build/lib.linux-x86_64-2.7/rebuildd/RebuilddNetworkClient.py
--- rebuildd-0.4.1/build/lib.linux-x86_64-2.7/rebuildd/RebuilddNetworkClient.py	2011-10-05 16:05:33.000000000 +0200
+++ rebuildd-0.4.2/build/lib.linux-x86_64-2.7/rebuildd/RebuilddNetworkClient.py	2012-07-24 10:59:36.000000000 +0200
@@ -135,6 +135,9 @@
         if len(args) > 0 and args[0] == "status":
             return self.exec_cmd_job_status(*args)
 
+        if len(args) > 0 and args[0] == "requeue":
+            return self.exec_cmd_job_requeue(*args)
+
         return "E: usage: job <command> [args]\n"
 
     def exec_cmd_job_add(self, *args):
@@ -169,12 +172,21 @@
             return "I: job added\n"
         return "E: error adding job\n"
 
+    def exec_cmd_job_requeue(self, *args):
+        """Requeue job"""
+
+        if len(args) != 2:
+            return "E: usage: job requeue <job_id>\n"
+
+        if self.rebuildd.requeue_job(job_id=int(args[1])):
+            return "I: job requeued\n"
+        return "E: error requeuing the job\n"
 
     def exec_cmd_job_deps(self, *args):
         """Add dependency"""
 
         ret = False
-        if len(args) < 2:
+        if len(args) < 3:
             return "E: usage: job deps <job_id> <dependency_job_id> [dependency_job_id] [...]\n"
 
 	ret = self.rebuildd.add_deps(job_id=args[1],
diff -Nru rebuildd-0.4.1/build/lib.linux-x86_64-2.7/rebuildd/Rebuildd.py rebuildd-0.4.2/build/lib.linux-x86_64-2.7/rebuildd/Rebuildd.py
--- rebuildd-0.4.1/build/lib.linux-x86_64-2.7/rebuildd/Rebuildd.py	2012-02-13 12:11:47.000000000 +0100
+++ rebuildd-0.4.2/build/lib.linux-x86_64-2.7/rebuildd/Rebuildd.py	2012-07-24 10:59:36.000000000 +0200
@@ -293,7 +293,7 @@
             arch = self.cfg.arch[0]
 
         if not Dists().get_dist(dist, arch):
-            RebuilddLog.error("Couldn't find dist/arch in the config file for %s_%s on %s/%s, don't adding it" \
+            RebuilddLog.error("Couldn't find dist/arch in the config file for %s_%s on %s/%s, not adding it" \
                            % (name, version, dist, arch))
             return False
 
@@ -307,7 +307,7 @@
 
         jobs_count = Job.selectBy(package=pkg, dist=dist, arch=arch, mailto=mailto, status=JobStatus.WAIT).count()
         if jobs_count:
-            RebuilddLog.error("Job already existing for %s_%s on %s/%s, don't adding it" \
+            RebuilddLog.error("Job already existing for %s_%s on %s/%s, not adding it" \
                            % (pkg.name, pkg.version, dist, arch))
             return False
 
@@ -322,6 +322,20 @@
                       % (name, version, dist, arch, mailto))
         return True
 
+    def requeue_job(self, job_id):
+        """Requeue a failed job"""
+
+        if Job.selectBy(id=job_id).count() == 0:
+            RebuilddLog.error("There is no job related to %s that is in the job list" % job_id)
+            return False
+        job = Job.selectBy(id=job_id)[0]
+
+        if job.status in FailedStatus:
+            job.status = JobStatus.WAIT
+            job.host = ""
+
+        return True
+
     def add_deps(self, job_id, dependency_ids):
 
         if Job.selectBy(id=job_id).count() == 0:
diff -Nru rebuildd-0.4.1/debian/changelog rebuildd-0.4.2/debian/changelog
--- rebuildd-0.4.1/debian/changelog	2012-03-09 11:48:42.000000000 +0100
+++ rebuildd-0.4.2/debian/changelog	2012-07-24 11:12:42.000000000 +0200
@@ -1,3 +1,23 @@
+rebuildd (0.4.2) unstable; urgency=low
+
+  [ Gauvain Pocentek ]
+  * Remove unused code (Closes: #671635)
+  * Support multiple recipients
+  * Improve some error messages
+  * Add a requeue command
+  * Fix the number of arguments for jobs deps
+  * Allow to use sendmail
+  * Change the default date format
+  * Add no_system_arch option
+  * Reload before adding arch
+
+  [ Julien Danjou ]
+  * Update default dists to symbolic names
+  * Fix PBUILDER_OTHER_OPTIONS[] examples in rebuildd.default
+    (Closes: #682422)
+
+ -- Julien Danjou <acid@debian.org>  Thu, 24 May 2012 16:50:03 +0200
+
 rebuildd (0.4.1) unstable; urgency=low
 
   [ Daniel Dehennin ]
diff -Nru rebuildd-0.4.1/debian/rebuildd.default rebuildd-0.4.2/debian/rebuildd.default
--- rebuildd-0.4.1/debian/rebuildd.default	2011-10-05 16:05:33.000000000 +0200
+++ rebuildd-0.4.2/debian/rebuildd.default	2012-07-24 11:09:24.000000000 +0200
@@ -21,12 +21,14 @@
 # PBUILDER_MIRROR=http://ftp.debian.org/debian
 
 # Pass other options to pbuilder
-# PBUILDER_OTHER_OPTIONS[0]="--components=main contrib"
-# PBUILDER_OTHER_OPTIONS[1]="--othermirror=deb http://my.apt.repo sid main"
+# PBUILDER_OTHER_OPTIONS[0]="--components"
+# PBUILDER_OTHER_OPTIONS[1]="main contrib"
+# PBUILDER_OTHER_OPTIONS[2]="--othermirror"
+# PBUILDER_OTHER_OPTIONS[3]="deb http://my.apt.repo sid main"
 
 # Distributions to generate and manage
 ARCHS="$(dpkg --print-architecture)"
-DISTS="etch lenny sid"
+DISTS="stable testing unstable"
 
 # Set to 1 to enable pbuilder/cowbuilder update in cron
 ENABLE_BUILDER_MAINT=0
diff -Nru rebuildd-0.4.1/debian/rebuildd-job.manpage.xml rebuildd-0.4.2/debian/rebuildd-job.manpage.xml
--- rebuildd-0.4.1/debian/rebuildd-job.manpage.xml	2011-10-05 16:05:33.000000000 +0200
+++ rebuildd-0.4.2/debian/rebuildd-job.manpage.xml	2012-07-24 10:59:36.000000000 +0200
@@ -87,6 +87,15 @@
         </listitem>
       </varlistentry>
       <varlistentry>
+        <term><option>requeue [jobid]</option>
+        </term>
+        <listitem>
+            <para>Reschedule a job.
+                The job id has to be provided as argument. The job must be in
+                an error state (previous build failed).</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
         <term><option>delete [jobid]</option>
         </term>
         <listitem>
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/rebuildd/Distribution.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/rebuildd/Distribution.pyc differ
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/rebuildd/Dists.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/rebuildd/Dists.pyc differ
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/rebuildd/Enumeration.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/rebuildd/Enumeration.pyc differ
diff -Nru rebuildd-0.4.1/rebuildd/__init__.py rebuildd-0.4.2/rebuildd/__init__.py
--- rebuildd-0.4.1/rebuildd/__init__.py	2011-10-05 16:05:33.000000000 +0200
+++ rebuildd-0.4.2/rebuildd/__init__.py	2012-07-24 10:59:36.000000000 +0200
@@ -19,8 +19,6 @@
 """rebuildd - Debian packages rebuild tool"""
 
 __author__  = "Julien Danjou <acid@debian.org>"
-__cvsid__   = "$Id$"
-__version__ = "$Rev$"[6:-2]
 __all__ = [
         "Distribution",
         "Enumeration",
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/rebuildd/__init__.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/rebuildd/__init__.pyc differ
diff -Nru rebuildd-0.4.1/rebuildd/Job.py rebuildd-0.4.2/rebuildd/Job.py
--- rebuildd-0.4.1/rebuildd/Job.py	2012-02-13 12:11:47.000000000 +0100
+++ rebuildd-0.4.2/rebuildd/Job.py	2012-07-24 10:59:36.000000000 +0200
@@ -20,6 +20,7 @@
 
 import threading, subprocess, smtplib, time, os, signal, socket, select
 import sqlobject
+from subprocess import Popen,PIPE
 from email.Message import Message
 from Dists import Dists
 from JobStatus import JobStatus
@@ -52,8 +53,6 @@
 
         threading.Thread.__init__(self)
         sqlobject.SQLObject.__init__(self, *args, **kwargs)
-        if self.log is None:
-            log = Log(job=self)
 
         self.do_quit = threading.Event()
         self.status_lock = threading.Lock()
@@ -201,7 +200,8 @@
             return False
 
         # Store in database
-        self.log.text = log
+        if self.log:
+            self.log.text = log
 
         with self.status_lock:
             if self.status != JobStatus.BUILD_OK and \
@@ -243,16 +243,15 @@
             smtp = smtplib.SMTP()
             smtp.connect(RebuilddConfig().get('mail', 'smtp_host'),
                          RebuilddConfig().get('mail', 'smtp_port'))
-            if self.mailto:
-                smtp.sendmail(RebuilddConfig().get('mail', 'from'),
-                              self.mailto,
-                              msg.as_string())
-            else:
-                smtp.sendmail(RebuilddConfig().get('mail', 'from'),
-                              RebuilddConfig().get('mail', 'mailto'),
-                              msg.as_string())
+            smtp.sendmail(RebuilddConfig().get('mail', 'from'),
+                          [m.strip() for m in msg['To'].split(",")],
+                          msg.as_string())
         except Exception, error:
-            RebuilddLog.error("Unable to send build log mail for job %d: %s" % (self.id, error))
+            try:
+                process = Popen("sendmail", shell=True, stdin=PIPE)
+                process.communicate(input=msg.as_string())
+            except:
+                RebuilddLog.error("Unable to send build log mail for job %d: %s" % (self.id, error))
 
         return True
 
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/rebuildd/Job.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/rebuildd/Job.pyc differ
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/rebuildd/JobStatus.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/rebuildd/JobStatus.pyc differ
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/rebuildd/Package.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/rebuildd/Package.pyc differ
diff -Nru rebuildd-0.4.1/rebuildd/RebuilddConfig.py rebuildd-0.4.2/rebuildd/RebuilddConfig.py
--- rebuildd-0.4.1/rebuildd/RebuilddConfig.py	2011-10-05 16:05:33.000000000 +0200
+++ rebuildd-0.4.2/rebuildd/RebuilddConfig.py	2012-07-24 10:59:36.000000000 +0200
@@ -53,6 +53,7 @@
         self.set('build', 'database_uri', 'sqlite:///var/lib/rebuildd/rebuildd.db')
         self.set('build', 'build_more_recent', '1')
         self.set('build', 'more_archs', 'any')
+        self.set('build', 'no_system_arch', '0')
 
         self.set('mail', 'from', 'rebuildd@localhost')
         self.set('mail', 'mailto', 'rebuildd@localhost')
@@ -74,19 +75,20 @@
         self.set('http', 'logfile', '/var/log/rebuildd/httpd.log')
 
         self.set('log', 'file', "/var/log/rebuildd/rebuildd.log")
-        self.set('log', 'time_format', "%d-%m-%Y %H:%M:%S")
+        self.set('log', 'time_format', "%Y-%m-%d %H:%M:%S")
         self.set('log', 'logs_dir', "/var/log/rebuildd/build_logs")
         self.set('log', 'mail_failed', '1')
         self.set('log', 'mail_successful', '0')
 
-        self.arch = []
-        parch = os.popen("dpkg --print-architecture")
-        self.arch.append(parch.readline().strip())
-        parch.close()
-
         if not dontparse:
             self.reload()
 
+        self.arch = []
+        if self.getint('build', 'no_system_arch') == 0:
+            parch = os.popen("dpkg --print-architecture")
+            self.arch.append(parch.readline().strip())
+            parch.close()
+
         for a in self.get('build', 'more_archs').split(' '):
             self.arch.append(a)
 
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/rebuildd/RebuilddConfig.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/rebuildd/RebuilddConfig.pyc differ
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/rebuildd/RebuilddLog.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/rebuildd/RebuilddLog.pyc differ
diff -Nru rebuildd-0.4.1/rebuildd/RebuilddNetworkClient.py rebuildd-0.4.2/rebuildd/RebuilddNetworkClient.py
--- rebuildd-0.4.1/rebuildd/RebuilddNetworkClient.py	2011-10-05 16:05:33.000000000 +0200
+++ rebuildd-0.4.2/rebuildd/RebuilddNetworkClient.py	2012-07-24 10:59:36.000000000 +0200
@@ -135,6 +135,9 @@
         if len(args) > 0 and args[0] == "status":
             return self.exec_cmd_job_status(*args)
 
+        if len(args) > 0 and args[0] == "requeue":
+            return self.exec_cmd_job_requeue(*args)
+
         return "E: usage: job <command> [args]\n"
 
     def exec_cmd_job_add(self, *args):
@@ -169,12 +172,21 @@
             return "I: job added\n"
         return "E: error adding job\n"
 
+    def exec_cmd_job_requeue(self, *args):
+        """Requeue job"""
+
+        if len(args) != 2:
+            return "E: usage: job requeue <job_id>\n"
+
+        if self.rebuildd.requeue_job(job_id=int(args[1])):
+            return "I: job requeued\n"
+        return "E: error requeuing the job\n"
 
     def exec_cmd_job_deps(self, *args):
         """Add dependency"""
 
         ret = False
-        if len(args) < 2:
+        if len(args) < 3:
             return "E: usage: job deps <job_id> <dependency_job_id> [dependency_job_id] [...]\n"
 
 	ret = self.rebuildd.add_deps(job_id=args[1],
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/rebuildd/RebuilddNetworkClient.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/rebuildd/RebuilddNetworkClient.pyc differ
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/rebuildd/RebuilddNetworkServer.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/rebuildd/RebuilddNetworkServer.pyc differ
diff -Nru rebuildd-0.4.1/rebuildd/Rebuildd.py rebuildd-0.4.2/rebuildd/Rebuildd.py
--- rebuildd-0.4.1/rebuildd/Rebuildd.py	2012-02-13 12:11:47.000000000 +0100
+++ rebuildd-0.4.2/rebuildd/Rebuildd.py	2012-07-24 10:59:36.000000000 +0200
@@ -293,7 +293,7 @@
             arch = self.cfg.arch[0]
 
         if not Dists().get_dist(dist, arch):
-            RebuilddLog.error("Couldn't find dist/arch in the config file for %s_%s on %s/%s, don't adding it" \
+            RebuilddLog.error("Couldn't find dist/arch in the config file for %s_%s on %s/%s, not adding it" \
                            % (name, version, dist, arch))
             return False
 
@@ -307,7 +307,7 @@
 
         jobs_count = Job.selectBy(package=pkg, dist=dist, arch=arch, mailto=mailto, status=JobStatus.WAIT).count()
         if jobs_count:
-            RebuilddLog.error("Job already existing for %s_%s on %s/%s, don't adding it" \
+            RebuilddLog.error("Job already existing for %s_%s on %s/%s, not adding it" \
                            % (pkg.name, pkg.version, dist, arch))
             return False
 
@@ -322,6 +322,20 @@
                       % (name, version, dist, arch, mailto))
         return True
 
+    def requeue_job(self, job_id):
+        """Requeue a failed job"""
+
+        if Job.selectBy(id=job_id).count() == 0:
+            RebuilddLog.error("There is no job related to %s that is in the job list" % job_id)
+            return False
+        job = Job.selectBy(id=job_id)[0]
+
+        if job.status in FailedStatus:
+            job.status = JobStatus.WAIT
+            job.host = ""
+
+        return True
+
     def add_deps(self, job_id, dependency_ids):
 
         if Job.selectBy(id=job_id).count() == 0:
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/rebuildd/Rebuildd.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/rebuildd/Rebuildd.pyc differ
diff -Nru rebuildd-0.4.1/rebuildd-job rebuildd-0.4.2/rebuildd-job
--- rebuildd-0.4.1/rebuildd-job	2011-10-05 16:05:33.000000000 +0200
+++ rebuildd-0.4.2/rebuildd-job	2012-07-24 10:59:36.000000000 +0200
@@ -32,6 +32,7 @@
     print "   add                     - add jobs, reading from stdin"
     print "   add-deps                - add build-depends, reading from stdin"
     print "   add-quinn-diff <dist>   - add package reading quinn-diff input from stdin"
+    print "   requeue <jobid>         - requeue job for rebuild"
     print "   delete <jobid>          - delete job"
     print "   list-deps               - list the whole depends table"
     print "   list <criteria>=<value> - list jobs matching criteria"
@@ -84,6 +85,11 @@
         priority = line.split('/')[1].split('_')[1].split('.dsc')[1].split(':')[0][2:]
         Rebuildd().add_job(name, version, priority, dist)
 
+def requeue_job(jobid):
+    if not Rebuildd().requeue_job(jobid):
+        print "E: rebuild not accepted"
+        sys.exit(1)
+
 def list():
     if len(sys.argv) == 3:
         try:
@@ -214,5 +220,7 @@
         stats()
     if sys.argv[1] == "delete" and len(sys.argv) > 2:
         delete(int(sys.argv[2]))
+    if sys.argv[1] == "requeue" and len(sys.argv) == 3:
+        requeue_job(int(sys.argv[2]))
 else:
     usage()
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/tests/RebuilddTestSetup.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/tests/RebuilddTestSetup.pyc differ
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/tests/TestDistribution.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/tests/TestDistribution.pyc differ
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/tests/TestJob.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/tests/TestJob.pyc differ
Binary files /tmp/Xvndfh3Gc9/rebuildd-0.4.1/tests/TestRebuildd.pyc and /tmp/DPQUc59v_l/rebuildd-0.4.2/tests/TestRebuildd.pyc differ


unblock rebuildd/0.4.2

-- System Information:
Debian Release: 7.0
  APT prefers unstable
  APT policy: (500, 'unstable'), (1, 'experimental')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 3.7-trunk-amd64 (SMP w/4 CPU cores)
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash

--- End Message ---
--- Begin Message ---
On 2013-03-20 08:38, Raphael Hertzog wrote:
Control: tags -1 - moreinfo

On Tue, 19 Mar 2013, Neil McGovern wrote:
Is there likely to be a t-p-u upload fixing the RC bug only, ie: the
patch in http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=671635#20, or
should I look for a removal?

0.4.1.1 uploaded with the following debdiff:

Approved, thanks.

--
Jonathan Wiltshire                                      jmw@debian.org
Debian Developer                         http://people.debian.org/~jmw

4096R: 0xD3524C51 / 0A55 B7C5 1223 3942 86EC  74C3 5394 479D D352 4C51

<directhex> i have six years of solaris sysadmin experience, from
            8->10. i am well qualified to say it is made from bonghits
			layered on top of bonghits

--- End Message ---

Reply to: