Julien Jehannet avatar Julien Jehannet committed 26a7c8b

(mq: checkpoint)

Comments (0)

Files changed (20)

 # HG changeset patch
-# Date 1304292456 -7200
+# Date 1304623868 -7200
 # User Julien Jehannet <julien@smaf.org>
-# Parent 3847f9529d4c781cfcb4848dd5a7a68f71f8bf93
+# Parent e856c1a694f01a64ffb9721b196ab6c72b471782
 
 diff --git a/TODOLIST b/TODOLIST
 new file mode 100644
 --- /dev/null
 +++ b/TODOLIST
-@@ -0,0 +1,29 @@
+@@ -0,0 +1,42 @@
 +preparedist
 +-----------
 +
 +- check/create template
 +- virer ask, confirm, condexec et utiliser lgc
 +
-+
 +check
 +-----
-+-i --interactive
++* -i --interactive
++	build doc if not up to date (-q)
++	close changelog
 +
-+build doc if not up to date (-q)
-+close changelog
++* check_debsrc
 +
-+check_debsrc
++* check preparedist
++  -s preparedist ?
 +
-+check preparedist ? 
-+-s preparedist ?
++* check_hg_repo (à faire aussi dans le build ?)
 +
++	vérifier les changesets incomings
++	vérifier les patches appliqués
++	>>> from mercurial.dispatch import dispatch as hg_call
++	>>> hg_call(['status'])
++
++build
++-----
++* ajouter la possibilité de lancer les checks en plus ?
++  --checks  yes/no
++  (utiliser un process en parallèle ? mais il faut régler tous les checkers bloquants avant)
++
++* copy files to resultdir
++  new copy back system (using dedicated thread)
 +
 +templates
 +---------
++new-project
 +copying
 +announce
-+new-project
 diff --git a/doc/lgp/bug.rst b/doc/lgp/bug.rst
 new file mode 100644
 --- /dev/null

build-multiprocess.diff

-# HG changeset patch
-# Date 1304547220 -7200
-# User Julien Jehannet <julien@smaf.org>
-# Parent eca50ae9f8eb98a8225704542b47c8c783d23cf0
-lgp: limit number of parallel builds (I/O bound)
-
-diff --git a/lgp/build.py b/lgp/build.py
---- a/lgp/build.py
-+++ b/lgp/build.py
-@@ -19,10 +19,11 @@
- import os
- import sys
- import logging
-+from time import time
- from glob import glob
- import os.path as osp
- from subprocess import Popen
--from multiprocessing import Process, BoundedSemaphore
-+from multiprocessing import Process, cpu_count, BoundedSemaphore
- 
- from logilab.devtools.lgp import (LGP, CONFIG_FILE, HOOKS_DIR, utils)
- from logilab.devtools.lgp.exceptions import LGPException
-@@ -34,24 +35,6 @@ from logilab.devtools.lgp.build_source i
- from logilab.devtools.lgp.clean import Cleaner
- 
- 
--def run_post_treatments(builder, distrib):
--    """ Run actions after package compiling """
--    # dpkg-scanpackages i386 /dev/null | gzip -9c > 386/Packages.gz
--    # dpkg-scanpackages amd64 /dev/null | gzip -9c > amd64/Packages.gz
--    # dpkg-scansources source /dev/null | gzip -9c > source/Sources.gz
--    resultdir = builder.get_distrib_dir(distrib)
--    with os.chdir(osp.dirname(resultdir)):
--        try:
--            cmd = "dpkg-scanpackages -m %s /dev/null 2>/dev/null | gzip -9c > %s/Packages.gz"
--            os.system(cmd % (distrib, distrib))
--        except Exception, err:
--            logging.warning("cannot update Debian trivial repository for '%s'"
--                            % resultdir)
--        else:
--            logging.debug("Debian trivial repository in '%s' updated."
--                          % resultdir)
--
--
- @LGP.register
- class Builder(SetupInfo):
-     """Provides functions to build a debian package for a python package
-@@ -63,7 +46,8 @@ class Builder(SetupInfo):
-                 {'type': 'string',
-                  'default': '', # check if new HOOKS_DIR
-                  'dest' : "hooks",
--                 'help': "run pbuilder hooks in '%s'" % HOOKS_DIR,
-+                 'help': ("run pbuilder hooks from '%s' directory or give "
-+                          "'NO' if no hook must be executed" % HOOKS_DIR),
-                  'group': 'Pbuilder'
-                 }),
-                ('post-treatments',
-@@ -73,20 +57,19 @@ class Builder(SetupInfo):
-                  'help': "run embedded post-treatments: add trivial repository",
-                  'group': 'Debian'
-                 }),
--               ('parallel',
-+               ('jobs',
-                 {'type': 'int',
--                 'default': 1,
--                 'dest' : "parallel",
--                 'help': "run all distribution builds in parallel (will disable verbose mode)",
-+                 'default': cpu_count(),
-+                 'short': 'j',
-+                 'dest' : "jobs",
-+                 'help': "run in parallel with a max limit of processes",
-                  'group': 'Build',
-                 }),
-               ]
--    # global build status
--    build_status = os.EX_OK
-     # list of all temporary directories
--    _tmpdirs = []
--    # hotlist of the recent generated package files
--    packages = []
-+    _build_tmpdirs = []
-+    # list of parallel build jobs
-+    joblist = []
- 
-     def _prune_pkg_dir(self):
-         super(Builder, self)._prune_pkg_dir()
-@@ -95,138 +78,122 @@ class Builder(SetupInfo):
-                    "Lgp expects debian directory from here.")
-             raise LGPException(msg)
- 
-+    def guess_environment(self):
-+        # if no default value for distribution, use list from existing images
-+        if self.config.distrib is None:
-+            self.config.distrib = 'all'
-+        super(Builder, self).guess_environment()
-+
-     def run(self, args):
-         Cleaner(None).run(args)
--
--        # create the upstream tarball if necessary and move it to result directory
-+        
-         with build_context(self.config.keep_tmpdir) as tmpdir:
-             pristine_tarball = Pristine(None).make_orig_tarball(tmpdir)
- 
--            try:
--                dsc_builder = DscBuilder(None)
--                dsc_builder.config.pkg_dir = self.config.pkg_dir # XXX
-+            jobs = self.config.jobs > 0 and self.config.jobs or 1
-+            self.available_procs = BoundedSemaphore(jobs)
- 
--                if utils.is_architecture_independent():
--                    arch= self.get_architectures(['current'])[0]
--                    msg = ('this binary build is arch-independent. Lgp will '
--                           'only build on current architecture (%s)')
--                    logging.info(msg, arch)
-+            dsc_builder = DscBuilder(None)
-+            dsc_builder.config.pkg_dir = self.config.pkg_dir # XXX
- 
--                if self.config.parallel > 1:
--                    logging.info('Lgp will limit output log in verbose mode for parallel builds')
-+            if utils.is_architecture_independent():
-+                arch= self.get_architectures(['current'])[0]
-+                msg = ('this binary build is arch-independent. Lgp will '
-+                       'only build on current architecture (%s)')
-+                self.logger.info(msg, arch)
- 
--                available_procs = BoundedSemaphore(self.config.parallel)
--                for distrib in self.distributions:
--                    # create a debian source package
--                    dscfile = dsc_builder.make_debian_source_package(pristine_tarball, distrib,
--                                                                     tmpdir=tmpdir)
--                    p = Process(target=self.make_debian_binary_package, args=(dscfile, distrib))
--                    available_procs.acquire()
--                    p.start()
--                    available_procs.release()
--                p.join()
-+            for distrib in self.distributions:
-+                dscfile = dsc_builder.make_debian_source_package(pristine_tarball, distrib,
-+                                                                 tmpdir=tmpdir)
-+                self.make_debian_binary_package(dscfile, distrib, tmpdir=tmpdir)
-+            self.logger.info("start parallel binary build(s) in background (limit: %s) ...",
-+                             jobs)
-+            
-+            # waiting for all child processes
-+            [p.join() for p in self.joblist]
-+            
-+            # move Debian binary package(s) files
-+            for tmp in self._build_tmpdirs:
-+                distrib, arch = tmp.split(os.sep)[-2:]
-+                changes  = glob(osp.join(tmp, '*.changes'))
-+                buildlog = glob(osp.join(tmp, '*.log'))
-+                self.move_package_files(changes + buildlog,
-+                                        self.get_distrib_dir(distrib))
-+                # do post-treatments only for a successful binary build
-+                if changes and self.config.post_treatments:
-+                    self.run_post_treatments(distrib)
-+        
-+        errors = [p.name for p in self.joblist if p.exitcode]
-+        if errors:
-+            self.logger.error("binary build failure(s): %s", ", ".join(errors))
-+        
-+        # return unix exit code
-+        return sum([p.exitcode for p in self.joblist])
- 
--            except LGPException, exc:
--                if getattr(self.config, "verbose", False):
--                    import traceback
--                    logging.critical(traceback.format_exc())
--                raise exc
--
--        return self.destroy_tmp_context()
--
--    def _builder_command(self, build_vars, dscfile):
--        # TODO Manage DEB_BUILD_OPTIONS
--        # http://www.debian.org/doc/debian-policy/ch-source.html
--        debuilder = os.environ.get('DEBUILDER', 'pbuilder')
--        logging.debug("package builder flavour: '%s'" % debuilder)
--        if debuilder == 'pbuilder':
--            # TODO encapsulate builder logic into specific InternalBuilder class
--            cmd = ['sudo', 'IMAGE=%(image)s' % build_vars,
--                   'DIST=%(distrib)s' % build_vars,
--                   'ARCH=%(arch)s' % build_vars,
--                   debuilder, 'build',
--                   '--configfile', CONFIG_FILE,
--                   '--buildresult', self._tmpdir]
--            if self.config.verbose == 3: # i.e. -vvv in command line
--                cmd.append('--debug')
--            if build_vars["buildopts"]:
--                cmd.extend(['--debbuildopts', "%(buildopts)s" % build_vars])
--            if self.config.hooks != "no":
--                cmd.extend(['--hookdir', HOOKS_DIR])
--            cmd.append(dscfile)
--        # XXX abstract builder methods
--        elif debuilder == 'debuild':
--            cmd = ['debuild', '--no-tgz-check', '--no-lintian',
--                   '--clear-hooks', '-uc', '-us']
--        elif debuilder == 'fakeroot':
--            cmd = ['fakeroot', 'debian/rules', 'binary']
--        else:
--            cmd = debuilder.split()
--        return cmd
--
--    def make_debian_binary_package(self, dscfile, distrib):
-+    def make_debian_binary_package(self, dscfile, distrib, tmpdir="."):
-         """create debian binary package(s)
- 
-         virtualize/parallelize the binary package build process
-         This is a rudimentary multiprocess support for parallel build by architecture
- 
-         Display build log when verbose mode is greater or equal to 2 (-vv*)
-+        """
-+        for build in self.use_build_series(distrib):
-+            build['builddir'] = os.path.join(tmpdir, build['distrib'], build['arch'])
-+            build['dscfile'] = dscfile
- 
--        :todo: use multiprocessing module here (python 2.6)
-+            os.mkdir(build['builddir'])
-+            self._build_tmpdirs.append(build['builddir'])
-+
-+            p = Process(name="%s/%s" % (build['distrib'], build['arch']),
-+                        target=self._run_build_process, args=((build,)),
-+                        kwargs={'verbose': self.config.verbose})
-+            p.start()
-+            self.joblist.append(p)
-+
-+    def _run_build_process(self, build, verbose=0):
-+        """internal (p)builder call
-+        
-+        This function have to be called as a new Process instance
-+        (see exit() method at the end)
-         """
-+        self.available_procs.acquire()
-+
-+        start_time = time()
-         stdout = {False: file(os.devnull, "w"), True: sys.stdout}
--        stdout = stdout[self.config.verbose >= 2] # i.e. -vv* in command line
--        joblist = []
--        tmplist = []
--        for build in self.use_build_series(distrib):
--            # change directory context at each binary build
--            tmplist.append(self.create_tmp_context())
-+        stdout = stdout[verbose >= 2] # i.e. -vv* in command line
-+        loggername = "build-binary[%s/%s]" % (build["distrib"],
-+                                              "current" if build["indep"]
-+                                              else build["arch"])
-+        logger = logging.getLogger(loggername)
- 
--            cmd = self._builder_command(build, dscfile)
--            # TODO manage handy --othermirror to use local mirror
--            #cmd.append(['--othermirror', "deb file:///home/juj/dists %s/" % build['distrib']])
--            logging.info("building Debian binary package for '%s/%s' "
--                         "using DEBBUILDOPTS options: '%s' ..."
--                         % (build['distrib'], build['arch'],
--                            build['buildopts'] or '(none)'))
-+        options = "using DEBBUILDOPTS options: %s " % build['buildopts'] if build['buildopts'] else ""
-+        logger.info("building Debian binary package %s...", options)
- 
--            logging.debug("running build command: %s ..." % ' '.join(cmd))
--            try:
--                joblist.append(Popen(cmd,
--                                     env={'DIST':  build['distrib'],
--                                          'ARCH':  build['arch'],
--                                          'IMAGE': build['image']},
--                                     stdout=stdout))
--            except Exception, err:
--                logging.critical(err)
--                logging.critical("build failure (%s/%s) for %s (%s)"
--                                 % (build['distrib'],
--                                    build['arch'],
--                                    self.get_debian_name(),
--                                    self.get_debian_version()))
--                return False
--
--        # only print dots in verbose mode (verbose: 1)
--        build_status, timedelta = utils.wait_jobs(joblist, self.config.verbose == 1)
--        if build_status:
--            logging.critical("binary build(s) failed for '%s' with exit status %d"
--                             % (build['distrib'], build_status))
-+        # format build command
-+        cmd = [c % build for c in self._builder_command(build)]
-+        logger.debug("running build command: %s ..." % ' '.join(cmd))
-+        try:
-+            p = Popen(cmd, stdout=stdout, env={'DIST':  build['distrib'],
-+                                               'ARCH':  build['arch'],
-+                                               'IMAGE': build['image']})
-+        except Exception, err:
-+            self.logger.critical(err)
-+            self.logger.critical("build failure (%s/%s) for %s (%s)"
-+                             % (build['distrib'],
-+                                build['arch'],
-+                                self.get_debian_name(),
-+                                self.get_debian_version()))
-+        exitcode = p.wait()
-+        self.available_procs.release()
-+        if exitcode:
-+            msg = "binary build failed with exit status %d"
-+            logger.error(msg, exitcode)
-         else:
--            logging.debug("binary build(s) for '%s' finished in %d seconds."
--                          % (build['distrib'], timedelta))
--
--        # move Debian binary package(s) files
--        for tmp in tmplist:
--            changes = glob(osp.join(tmp, '*.changes'))
--            buildlog = glob(osp.join(tmp, '*.log'))
--            self.move_package_files(changes + buildlog, self.get_distrib_dir(distrib))
--
--        self.build_status += build_status
--        status = build_status == os.EX_OK
--        # do post-treatments only for a successful binary build
--        if status and self.packages and self.config.post_treatments:
--            run_post_treatments(self, distrib)
--        return status
-+            logger.debug("binary build finished in %d seconds",
-+                         time()-start_time)
-+        # return exit code cos run as real subprocess
-+        sys.exit(exitcode)
- 
-     def use_build_series(self, distrib):
-         """create a series of binary build command
-@@ -254,6 +221,7 @@ class Builder(SetupInfo):
-         series = []
-         if utils.is_architecture_independent():
-             options = dict()
-+            options['indep'] = True
-             options['distrib'] = distrib
-             options['buildopts'] = _build_options()
-             options['arch'] = self.get_architectures(['current'])[0]
-@@ -263,6 +231,7 @@ class Builder(SetupInfo):
-         else:
-             for rank, arch in enumerate(self.architectures):
-                 options = dict()
-+                options['indep'] = False
-                 options['distrib'] = distrib
-                 options['buildopts'] = _build_options(arch, rank)
-                 options['arch'] = arch
-@@ -271,9 +240,51 @@ class Builder(SetupInfo):
-                 series.append(options)
-         return series
- 
-+    def _builder_command(self, build_vars):
-+        # TODO Manage DEB_BUILD_OPTIONS
-+        # http://www.debian.org/doc/debian-policy/ch-source.html
-+        # XXX manage handy --othermirror to use local mirror
-+        #cmd.append(['--othermirror', "deb file:///home/juj/dists %s/" % build['distrib']])
-+        debuilder = os.environ.get('DEBUILDER', 'pbuilder')
-+        self.logger.debug("package builder flavour: '%s'" % debuilder)
-+        if debuilder.startswith('pbuilder'): # pbuilder(-uml)
-+            # TODO encapsulate builder logic into specific InternalBuilder class
-+            cmd = ['sudo', 'IMAGE=%(image)s', 'DIST=%(distrib)s',
-+                   'ARCH=%(arch)s', debuilder, 'build',
-+                   '--configfile', CONFIG_FILE,
-+                   '--buildresult', '%(builddir)s']
-+            if self.config.verbose == 3: # i.e. -vvv in command line
-+                cmd.append('--debug')
-+            if build_vars["buildopts"]:
-+                cmd.extend(['--debbuildopts', "%(buildopts)s"])
-+            if self.config.hooks.lower() != "no":
-+                cmd.extend(['--hookdir', self.config.hooks])
-+            cmd.append('%(dscfile)s')
-+        # XXX abstract builder methods
-+        elif debuilder == 'debuild':
-+            cmd = ['debuild', '--no-tgz-check', '--no-lintian',
-+                   '--clear-hooks', '-uc', '-us']
-+        elif debuilder == 'fakeroot':
-+            cmd = ['fakeroot', 'debian/rules', 'binary']
-+        else:
-+            cmd = debuilder.split()
-+        assert isinstance(cmd, list)
-+        return cmd
- 
--    def guess_environment(self):
--        # if no default value for distribution, use list from existing images
--        if self.config.distrib is None:
--            self.config.distrib = 'all'
--        super(Builder, self).guess_environment()
-+    def run_post_treatments(self, distrib):
-+        """ Run actions after package compiling """
-+        # dpkg-scanpackages i386 /dev/null | gzip -9c > 386/Packages.gz
-+        # dpkg-scanpackages amd64 /dev/null | gzip -9c > amd64/Packages.gz
-+        # dpkg-scansources source /dev/null | gzip -9c > source/Sources.gz
-+        resultdir = self.get_distrib_dir(distrib)
-+        packages_file = osp.join(resultdir, "Packages.gz")
-+        try:
-+            cmd = "cd %s && dpkg-scanpackages -m %s /dev/null 2>/dev/null | gzip -9c > %s/Packages.gz"
-+            os.system(cmd % (osp.dirname(resultdir), distrib, resultdir))
-+        except Exception, err:
-+            self.logger.warning("cannot update Debian trivial repository for '%s'"
-+                                % resultdir)
-+        else:
-+            self.logger.debug("Debian trivial repository in '%s' updated."
-+                              % packages_file)
-+
-diff --git a/lgp/build_pristine.py b/lgp/build_pristine.py
---- a/lgp/build_pristine.py
-+++ b/lgp/build_pristine.py
-@@ -139,8 +139,7 @@ class Pristine(SetupInfo):
-                                % (self.get_debian_name(), debian_revision))
- 
-         if os.path.exists(tarball):
--            logging.debug("will erase previous pristine tarball file: %s",
--                          tarball)
-+            logging.debug("will erase previous pristine tarball file: %s", tarball)
-             os.unlink(tarball)
- 
-         try:
-diff --git a/lgp/login.py b/lgp/login.py
---- a/lgp/login.py
-+++ b/lgp/login.py
-@@ -70,9 +70,10 @@ class Login(SetupInfo):
-                 image = self.get_basetgz(distrib, arch)
- 
-                 resultdir = self.get_distrib_dir(distrib)
-+                other_mirror = self.other_mirror(resultdir)
-                 cmd = self.cmd % (image, distrib, arch, self.sudo_cmd,
-                                   self.pbuilder_cmd, CONFIG_FILE, HOOKS_DIR,
--                                  resultdir, self.other_mirror(resultdir))
-+                                  resultdir, other_mirror)
- 
-                 logging.info("login into '%s/%s' image" % (distrib, arch))
-                 logging.debug("run command: %s", cmd)
-@@ -80,7 +81,7 @@ class Login(SetupInfo):
-                     check_call(cmd, shell=True,
-                                env={'DIST': distrib, 'ARCH': arch, 'IMAGE': image,
-                                     'DISPLAY': os.environ.get('DISPLAY', ""),
--                                    'OTHERMIRROR': self.other_mirror})
-+                                    'OTHERMIRROR': other_mirror})
-                 except CalledProcessError, err:
-                     logging.warn("returned non-zero exit status %s",
-                                  err.returncode)
-diff --git a/lgp/setupinfo.py b/lgp/setupinfo.py
---- a/lgp/setupinfo.py
-+++ b/lgp/setupinfo.py
-@@ -89,7 +89,6 @@ class SetupInfo(clcommands.Command):
-                 {'type': 'string',
-                  'hide': True,
-                  'dest': "pkg_dir",
--                 'short': 'p',
-                  'metavar' : "<root of the debian project directory>",
-                 }),
-                ('verbose',
-@@ -156,6 +155,7 @@ class SetupInfo(clcommands.Command):
-         self.options.extend(options) # merge parent options
-         super(SetupInfo, self).__init__(logger)
-         self._set_package_format()
-+        exitcode = os.EX_OK
- 
-     def main_run(self, arguments, rcfile):
-         # Load the global settings for lgp
-@@ -170,7 +170,7 @@ class SetupInfo(clcommands.Command):
-             return os.EX_OK
- 
-         # Set verbose level and output streams
--        if getattr(self.config, 'verbose', None):
-+        if self.config.verbose:
-             logging.getLogger().setLevel(logging.DEBUG)
-         elif getattr(self.config, 'quiet', None):
-             logging.getLogger().setLevel(logging.WARN)
-@@ -192,8 +192,14 @@ class SetupInfo(clcommands.Command):
-         # Some package formats expect a clean state with no troubling file
-         # (ex: distutils)...
-         self._prune_pkg_dir()
--        self.run(arguments)
--        return os.EX_OK
-+        try:
-+            self.exitcode = self.run(arguments)
-+        except LGPException, exc:
-+            if self.config.verbose:
-+                import traceback
-+                logging.critical(traceback.format_exc())
-+            raise exc
-+        return self.exitcode
- 
-     def go_into_package_dir(self, arguments):
-         """go into package directory"""
-@@ -508,32 +514,3 @@ class SetupInfo(clcommands.Command):
-                 raise
-         return distrib_dir
- 
--    def create_tmp_context(self, suffix=""):
--        """create new build temporary context
--
--        Each context (directory for now) will be cleaned at the end of the build
--        process by the destroy_tmp_context method"""
--        self._tmpdir = tempfile.mkdtemp(suffix)
--        logging.debug('changing build context... (%s)' % self._tmpdir )
--        self._tmpdirs.append(self._tmpdir)
--        return self._tmpdir
--
--    def destroy_tmp_context(self):
--        """clean all temporary build context and returns exit code"""
--        self.clean_tmpdirs()
--        return self.build_status
--
--    def clean_tmpdirs(self):
--        if not self.config.keep_tmpdir:
--            if hasattr(self, '_tmpdirs'):
--                for tmpdir in self._tmpdirs:
--                    try:
--                        shutil.rmtree(tmpdir)
--                    except OSError, exc:
--                        logging.error("cannot remove '%s' (%s)"
--                                      % (tmpdir, exc))
--        else:
--            contents = [(t, os.listdir(t)) for t in self._tmpdirs]
--            for t, c in contents:
--                logging.warn("temporary directory not deleted: %s (%s)"
--                             % (t, ", ".join(c)))
-diff --git a/lgp/utils.py b/lgp/utils.py
---- a/lgp/utils.py
-+++ b/lgp/utils.py
-@@ -18,7 +18,6 @@
- import glob
- import sys
- import os
--import time
- import os.path as osp
- import re
- from subprocess import Popen, PIPE
-@@ -211,22 +210,6 @@ def cached(func):
-             return decorated._once_result
-     return decorated
- 
--def wait_jobs(joblist, print_dots=True):
--    t0 = time.time()
--    status = 0
--    while joblist:
--        for j in joblist:
--            j.poll()
--            if j.returncode is not None:
--                status += j.returncode
--                joblist.remove(j)
--        if print_dots:
--            time.sleep(1)
--            sys.stderr.write('.')
--    if print_dots:
--        sys.stderr.write('\n')
--    return status, time.time() - t0
--
- def _parse_deb_distrib(changelog='debian/changelog'):
-     try:
-         return re.search("\) (.+);", open(changelog).readline()).group(1).strip()
 # HG changeset patch
-# Parent 215e497a8e83a211bfbf0a4e8bdce9b0494d1d9a
+# Date 1304802311 -7200
+# User Julien Jehannet <julien@smaf.org>
+# Parent 87b0743914ab16d444d95e2c73d90c050fccfd4a
+
+diff --git a/.hgtags b/.hgtags
+--- a/.hgtags
++++ b/.hgtags
+@@ -136,3 +136,4 @@ 16198ddfd78792688a5cd2a5209bcd29e1778b51
+ 1a47e33c41c5dda8191b037449accfd20e87a464 logilab-devtools-version-0.17.4-3
+ e48596ffcb3bcc9723bbf6ac01f9af95cfb0b414 logilab-devtools-version-0.17.5
+ 98faa304f783c495f14dc3e53399d4f36c3f3bfe logilab-devtools-debian-version-0.17.5-1
++a8f218adba20c6dfb51ee614c0512cad6ac9d78a AAAA
 diff --git a/lgp/build.py b/lgp/build.py
 --- a/lgp/build.py
 +++ b/lgp/build.py
-@@ -25,6 +25,8 @@ import os.path as osp
- from subprocess import Popen
- from multiprocessing import Process, cpu_count, BoundedSemaphore
- 
-+from debian import deb822
-+
- from logilab.devtools.lgp import (LGP, CONFIG_FILE, HOOKS_DIR, utils)
- from logilab.devtools.lgp.exceptions import LGPException
- from logilab.devtools.lgp.utils import build_context
-@@ -38,7 +40,9 @@ from logilab.devtools.lgp.clean import C
- @LGP.register
- class Builder(SetupInfo):
-     """Provides functions to build a debian package for a python package
--    You can change options in /etc/lgp/lgprc inside the [LGP-BUILD] section
-+    You can change options in /etc/lgp/lgprc inside the [BUILD] section
-+
-+    lgp build --help for more details.
+@@ -46,22 +46,22 @@ class Builder(SetupInfo):
      """
      name = "build"
      options = DscBuilder.options + [
-@@ -50,6 +54,12 @@ class Builder(SetupInfo):
-                  'help': "run in parallel with a max limit of processes",
-                  'group': 'Build',
+-               ('debug',
++               ('hooks',
+                 {'type': 'csv',
+-                  'dest': 'debug_hooks',
+-                  'short': 'D',
+-                  'metavar': "<debug hook>",
+-                  'help': "list of action hook to enable in debug mode"
+-                          "(available action: autoshell)",
++                  'dest': 'hooks',
++                  'short': 'H',
++                  'metavar': "<action hooks>",
++                  'help': "use '*' to enable Lgp hooks. "
++                          "You can tune them some by passing actions "
++                          "(available: autoshell, lintian)",
+                  'group': 'Pbuilder',
                  }),
-+               ('dscfile',
-+                {'type': 'string',
-+                 'dest' : "dscfile",
-+                 'help': 'use a specific Debian source file (.dsc) instead of recreating it',
-+                 'group': 'Build'
-+                }),
-                ('debug',
-                 {'type': 'csv',
-                   'dest': 'debug_hooks',
-@@ -89,35 +99,50 @@ class Builder(SetupInfo):
+-               # XXX should be enabled by default ?
+-               ('hooks',
++               ('hooks-dir',
+                 {'type': 'string',
+-                 'default': '', # check if new HOOKS_DIR
+-                 'dest' : "hooks",
+-                 'help': ("use yes/no to run pbuilder hooks in given directory."
+-                          "You can also pass your own hook directory (default: %s)" % HOOKS_DIR),
++                 'default': HOOKS_DIR,
++                 'dest' : "hooks_dir",
++                 'help': ("directory from where pbuilder will run hooks "
++                          "if enabled (default: %s)" % HOOKS_DIR),
+                  'group': 'Pbuilder'
+                 }),
+                ('post-treatments',
+@@ -99,6 +99,7 @@ class Builder(SetupInfo):
              raise LGPException(msg)
  
      def guess_environment(self):
-+        self.config.orig_tarball = self._normpath(self.config.orig_tarball)
-+        self.config.dscfile = self._normpath(self.config.dscfile)
-+        
-+        if self.config.orig_tarball:
-+            self.logger.info('use original source archive (tarball): %s',
-+                             self.config.orig_tarball)
-+        if self.config.dscfile:
-+            self.logger.info('use the Debian source control file: %s',
-+                             self.config.dscfile)
-+            contents = deb822.Deb822(file(self.config.dscfile))
-+            self.config.archi = contents["Architecture"]
-+        
-         # if no default value for distribution, use list from existing images
-         if self.config.distrib is None:
-             self.config.distrib = 'all'
-         super(Builder, self).guess_environment()
- 
-     def run(self, args):
--        Cleaner(None).run(args)
-+        Cleaner().run(args)
-+        
-+        jobs = self.config.jobs > 0 and self.config.jobs or 1
-+        self.available_procs = BoundedSemaphore(jobs)
-+        
-+        if utils.is_architecture_independent():
-+            arch= self.get_architectures(['current'])[0]
-+            msg = ('this binary build is arch-independent. Lgp will '
-+                   'only build on current architecture (%s)')
-+            self.logger.info(msg, arch)
++        self.config.hooks_dir = self._normpath(self.config.hooks_dir)
+         self.config.orig_tarball = self._normpath(self.config.orig_tarball)
+         self.config.dscfile = self._normpath(self.config.dscfile)
          
-         with build_context(self.config.keep_tmpdir) as tmpdir:
--            pristine_tarball = Pristine(None).make_orig_tarball(tmpdir)
--
--            jobs = self.config.jobs > 0 and self.config.jobs or 1
--            self.available_procs = BoundedSemaphore(jobs)
--
--            dsc_builder = DscBuilder(None)
--            dsc_builder.config.pkg_dir = self.config.pkg_dir # XXX
--
--            if utils.is_architecture_independent():
--                arch= self.get_architectures(['current'])[0]
--                msg = ('this binary build is arch-independent. Lgp will '
--                       'only build on current architecture (%s)')
--                self.logger.info(msg, arch)
--
-+            pristine = Pristine(config=self.config)
-+            pristine_tarball = pristine.make_orig_tarball(tmpdir=tmpdir)
-+            if self.config.dscfile is None:
-+                dsc_builder = DscBuilder(config=self.config)
-+            
-             for distrib in self.distributions:
--                dscfile = dsc_builder.make_debian_source_package(pristine_tarball, distrib,
--                                                                 tmpdir=tmpdir)
--                self.make_debian_binary_package(dscfile, distrib, tmpdir=tmpdir)
--            self.logger.info("start parallel binary build(s) in background (limit: %s) ...",
--                             jobs)
-+                if self.config.dscfile is None:
-+                    make_dsc = dsc_builder.make_debian_source_package
-+                    self.config.dscfile = make_dsc(pristine_tarball, distrib, tmpdir=tmpdir)
-+                self.make_debian_binary_package(self.config.dscfile, distrib, tmpdir=tmpdir)
-+            
-+            if len(self.joblist)>1:
-+                msg = "start parallel binary build(s) in background (limit: %s) ..."
-+                self.logger.info(msg, jobs)
-             
-             # waiting for all child processes
-             [p.join() for p in self.joblist]
-@@ -152,7 +177,8 @@ class Builder(SetupInfo):
-             build['builddir'] = os.path.join(tmpdir, build['distrib'], build['arch'])
-             build['dscfile'] = dscfile
- 
--            os.mkdir(build['builddir'])
-+            # multi-level creation needed if no source package
-+            os.makedirs(build['builddir'])
-             self._build_tmpdirs.append(build['builddir'])
- 
-             p = Process(name="%s/%s" % (build['distrib'], build['arch']),
+@@ -300,14 +301,17 @@ class Builder(SetupInfo):
+                 cmd.append('--debug')
+             if build_vars["buildopts"]:
+                 cmd.extend(['--debbuildopts', "%(buildopts)s"])
+-            if self.config.hooks.lower() != "no":
+-                cmd.extend(['--hookdir', self.config.hooks])
++            if self.config.hooks:
++                cmd.extend(['--hookdir', self.config.hooks_dir])
+             cmd.append('%(dscfile)s')
+-            # manage extra hook parameters used in debug mode
+-            if self.config.debug_hooks:
+-                self.config.debug_hooks.insert(0, ' --')
+-                actions = ' --'.join(self.config.debug_hooks)
+-                cmd.append(actions)
++            # manage extra hook actions
++            if self.config.hooks:
++                hooks = ['lintian', 'autoshell']
++                if '*' in self.config.hooks:
++                    self.config.hooks = hooks
++                self.config.hooks.insert(0, ' --')
++                hooks = ' --'.join(self.config.hooks)
++                cmd.append(hooks)
+         # XXX abstract builder methods
+         elif debbuilder == 'debuild':
+             cmd = ['debuild', '--no-tgz-check', '--no-lintian',

build_pristine.diff

-# HG changeset patch
-# Parent 7214b51d70bb5fe98f4cb1b83f4a833e15a9f659
-diff --git a/lgp/build_pristine.py b/lgp/build_pristine.py
---- a/lgp/build_pristine.py
-+++ b/lgp/build_pristine.py
-@@ -37,12 +37,13 @@ class Pristine(SetupInfo):
-     """
-     name = "build-pristine"
-     options = SetupInfo.options + [
--               ('orig-tarball',
-+               ('pristine-tarball',
-                 {'type': 'string',
-                  'default' : None,
-                  'dest': 'orig_tarball',
-                  'metavar' : "<tarball>",
--                 'help': "URI to orig.tar.gz file",
-+                 'help': "URI to pristine tarball orig.tar.gz file",
-+                 'group': 'Build',
-                 }),
-                ('keep-tmpdir',
-                 {'action': 'store_true',
-@@ -57,15 +58,15 @@ class Pristine(SetupInfo):
-     def guess_environment(self): pass
- 
-     def run(self, args):
--        Cleaner(None).run(args)
-+        Cleaner().run(args)
-         with build_context(self.config.keep_tmpdir) as tmpdir:
--            pristine_tarball = self.make_orig_tarball(tmpdir)
-+            pristine_tarball = self.make_orig_tarball(tmpdir=tmpdir)
-             fullpath = osp.join('..', osp.basename(pristine_tarball))
-             cp(pristine_tarball, fullpath)
-         msg= 'a new original source archive (tarball) is available: %s'
-         self.logger.info(msg, fullpath)
- 
--    def make_orig_tarball(self, tmpdir):
-+    def make_orig_tarball(self, tmpdir=None):
-         """make or retrieve upstream pristine tarballs
- 
-         Start by calling the optional get-orig-source from debian/rules

build_source.diff

-# HG changeset patch
-# Parent 83019fafb4a548096216705e65d7238fd09ea5fa
-diff --git a/lgp/build_source.py b/lgp/build_source.py
---- a/lgp/build_source.py
-+++ b/lgp/build_source.py
-@@ -70,16 +70,23 @@ class DscBuilder(SetupInfo):
-     packages = []
- 
-     def guess_environment(self):
-+        self.config.orig_tarball = self._normpath(self.config.orig_tarball)
-+        
-+        if self.config.orig_tarball:
-+            self.logger.info('use original source archive (tarball) is available: %s',
-+                             self.config.orig_tarball)
-+        
-         # if no default value for distribution, try to retrieve it from changelog
-         if self.config.distrib is None or 'changelog' in self.config.distrib:
-             self.config.distrib = 'changelog'
-         super(DscBuilder, self).guess_environment()
- 
-     def run(self, args):
--        Cleaner(None).run(args)
-+        Cleaner().run(args)
- 
-         with utils.build_context(self.config.keep_tmpdir) as tmpdir:
--            pristine_tarball = Pristine(None).make_orig_tarball(tmpdir)
-+            pristine = Pristine(config=self.config)
-+            pristine_tarball = pristine.make_orig_tarball(tmpdir=tmpdir)
- 
-             for distrib in self.distributions:
-                 resultdir = self.get_distrib_dir(distrib)

clcommands-build-pristine.diff

 # HG changeset patch
-# Date 1304112084 -7200
+# Date 1304618588 -7200
 # User Julien Jehannet <julien@smaf.org>
-# Parent 97d88c07fa0abb8ad58c40abd25b34a91e60c4b0
+# Parent de33865e5184689726812ae0147f8c416d8e0be2
 [command] build-pristine: new lgp command for pristine tarball creation
 
 New:
 new file mode 100644
 --- /dev/null
 +++ b/lgp/build_pristine.py
-@@ -0,0 +1,164 @@
+@@ -0,0 +1,173 @@
 +# -*- coding: utf-8 -*-
 +#
 +# Copyright (c) 2011 LOGILAB S.A. (Paris, FRANCE).
 +    """
 +    name = "build-pristine"
 +    options = SetupInfo.options + [
-+               ('orig-tarball',
++               ('pristine-tarball',
 +                {'type': 'string',
 +                 'default' : None,
 +                 'dest': 'orig_tarball',
 +                 'metavar' : "<tarball>",
-+                 'help': "URI to orig.tar.gz file",
++                 'help': "URI to pristine tarball orig.tar.gz file",
++                 'group': 'Build',
 +                }),
 +               ('keep-tmpdir',
 +                {'action': 'store_true',
 +                }),
 +              ]
 +
-+    def guess_environment(self): pass
++    def guess_environment(self):
++        self.config.orig_tarball = self._normpath(self.config.orig_tarball)
++        
++        if self.config.orig_tarball:
++            self.logger.info('use original source archive (tarball): %s',
++                             self.config.orig_tarball)
 +
 +    def run(self, args):
-+        Cleaner(None).run(args)
++        Cleaner().run(args)
 +        with build_context(self.config.keep_tmpdir) as tmpdir:
-+            pristine_tarball = self.make_orig_tarball(tmpdir)
++            pristine_tarball = self.make_orig_tarball(tmpdir=tmpdir)
 +            fullpath = osp.join('..', osp.basename(pristine_tarball))
 +            cp(pristine_tarball, fullpath)
 +        msg= 'a new original source archive (tarball) is available: %s'
 +        logging.info(msg, fullpath)
 +
-+    def make_orig_tarball(self, tmpdir):
++    def make_orig_tarball(self, tmpdir=None):
 +        """make or retrieve upstream pristine tarballs
 +
 +        Start by calling the optional get-orig-source from debian/rules
 +            self.config.orig_tarball.startswith(('ftp://', 'http://'))):
 +            logging.warn("retrieve pristine tarball remotely is not recommended")
 +
++        if self.config.orig_tarball and self.is_initial_debian_revision():
++            msg = "you are passing a pristine tarball in command line for an initial revision"
++            self.logger.warn(msg)
++
 +        # creation of source archive if not provided
-+        if self.is_initial_debian_revision():
++        if not self.config.orig_tarball and self.is_initial_debian_revision():
 +            logging.info("creating new pristine tarball from working directory...")
 +            try:
 +                self._run_command("sdist", dist_dir=tmpdir)
 +                               % (self.get_debian_name(), debian_revision))
 +
 +        if os.path.exists(tarball):
-+            logging.debug("will erase previous pristine tarball file: %s",
-+                          tarball)
++            logging.debug("will erase previous pristine tarball file: %s", tarball)
 +            os.unlink(tarball)
 +
 +        try:
  import tempfile
  from string import Template
  from distutils.core import run_setup
-@@ -391,87 +390,6 @@ class SetupInfo(clcommands.Command):
+@@ -396,87 +395,6 @@ class SetupInfo(clcommands.Command):
              msg %= (upstream_version, debian_upstream_version)
              raise LGPException(msg)
  
      def prepare_source_archive(self):
          """prepare and extract the upstream tarball
  
-@@ -481,7 +399,7 @@ class SetupInfo(clcommands.Command):
+@@ -486,7 +404,7 @@ class SetupInfo(clcommands.Command):
          self.create_tmp_context()
  
          # Mandatory to be compatible with format 1.0
  import logging
  
  from debian.deb822 import Deb822
-@@ -245,3 +248,15 @@ def _parse_deb_project(changelog='debian
+@@ -247,3 +250,15 @@ def _parse_deb_project(changelog='debian
      return re.search("^(.+?) ", open(changelog).readline()).group(1).strip()
  
  

clcommands-build-source.diff

 # HG changeset patch
-# Date 1304546827 -7200
+# Date 1304636566 -7200
 # User Julien Jehannet <julien@smaf.org>
-# Parent e1ff0c6bde2543b750c5b979d4b7a42355555739
-[command] build-source: new lgp command for Debian source archive creation and multiprocessing capabilities
+# Parent 9da51840090b0c5af4652b28f069da50e6892005
+[command] add multiprocessing capabilities and new build-source command
 
 New:
 * create parallel processes to build different distributions
+  (can limit number of parallel builds (I/O bound))
+* new command build-source command:
+  new lgp command for Debian source archive creation
+* new build option `-dscfile` to reuse Debian source control file (.dsc)
 
 Changes:
 * get rid of the 'current_distrib' instance variable which is not appropriate in parallel mode
   API changes for `get_distrib_dir()` and `get_debian_dir()`
 * get rid of internal variable `self.origpath`
 * limit number of build context creation
-* Use a SIGN_LOCK lock on debsign command to give a chance to input your passphrase once
+* refactor signing process and checker
+* use a SIGN_LOCK lock on debsign command to give a chance to input your passphrase once
 
 Need more refactoring:
 * `_prepare_source_archive()` run at beginning of `make_debian_source_package()`
 diff --git a/lgp/build.py b/lgp/build.py
 --- a/lgp/build.py
 +++ b/lgp/build.py
-@@ -18,25 +18,19 @@
+@@ -18,90 +18,42 @@
  
  import os
  import sys
  import logging
 -import hashlib
 -import errno
++from time import time
  from glob import glob
  import os.path as osp
 -from subprocess import check_call, CalledProcessError, Popen
++from subprocess import Popen
++from multiprocessing import Process, cpu_count, BoundedSemaphore
+ 
+ from debian import deb822
+ 
+-from logilab.common.shellutils import cp
 -
--from debian import deb822
--
--from logilab.common.shellutils import cp
-+from subprocess import Popen
-+from multiprocessing import Process, BoundedSemaphore
- 
  from logilab.devtools.lgp import (LGP, CONFIG_FILE, HOOKS_DIR, utils)
 -from logilab.devtools.lgp.exceptions import (LGPException, LGPCommandException)
 +from logilab.devtools.lgp.exceptions import LGPException
  from logilab.devtools.lgp.clean import Cleaner
  
  
-@@ -45,15 +39,17 @@ def run_post_treatments(builder, distrib
-     # dpkg-scanpackages i386 /dev/null | gzip -9c > 386/Packages.gz
-     # dpkg-scanpackages amd64 /dev/null | gzip -9c > amd64/Packages.gz
-     # dpkg-scansources source /dev/null | gzip -9c > source/Sources.gz
+-def run_post_treatments(builder, distrib):
+-    """ Run actions after package compiling """
+-    # dpkg-scanpackages i386 /dev/null | gzip -9c > 386/Packages.gz
+-    # dpkg-scanpackages amd64 /dev/null | gzip -9c > amd64/Packages.gz
+-    # dpkg-scansources source /dev/null | gzip -9c > source/Sources.gz
 -    distdir = builder.get_distrib_dir()
 -    with os.chdir(osp.dirname(distdir)):
-+    resultdir = builder.get_distrib_dir(distrib)
-+    with os.chdir(osp.dirname(resultdir)):
-         try:
-             cmd = "dpkg-scanpackages -m %s /dev/null 2>/dev/null | gzip -9c > %s/Packages.gz"
-             os.system(cmd % (distrib, distrib))
-         except Exception, err:
+-        try:
+-            cmd = "dpkg-scanpackages -m %s /dev/null 2>/dev/null | gzip -9c > %s/Packages.gz"
+-            os.system(cmd % (distrib, distrib))
+-        except Exception, err:
 -            logging.warning("cannot update Debian trivial repository for '%s'" % distdir)
-+            logging.warning("cannot update Debian trivial repository for '%s'"
-+                            % resultdir)
-         else:
+-        else:
 -            logging.debug("Debian trivial repository in '%s' updated." % distdir)
-+            logging.debug("Debian trivial repository in '%s' updated."
-+                          % resultdir)
- 
- 
+-
+-
  @LGP.register
-@@ -62,30 +58,7 @@ class Builder(SetupInfo):
-     You can change options in /etc/lgp/lgprc inside the [LGP-BUILD] section
+ class Builder(SetupInfo):
+     """Provides functions to build a debian package for a python package
+-    You can change options in /etc/lgp/lgprc inside the [LGP-BUILD] section
++    You can change options in /etc/lgp/lgprc inside the [BUILD] section
++
++    lgp build --help for more details.
      """
      name = "build"
 -    options = Pristine.options + [
                 ('hooks',
                  {'type': 'string',
                   'default': '', # check if new HOOKS_DIR
-@@ -93,15 +66,6 @@ class Builder(SetupInfo):
-                  'help': "run pbuilder hooks in '%s'" % HOOKS_DIR,
+                  'dest' : "hooks",
+-                 'help': "run pbuilder hooks in '%s'" % HOOKS_DIR,
++                 'help': ("use yes/no to run pbuilder hooks in given directory."
++                          "You can also pass your own hook directory (default: %s)" % HOOKS_DIR),
                   'group': 'Pbuilder'
                  }),
 -               # use yes/no types here to configure globally
                 ('post-treatments',
                  {'type': 'yn',
                   'default': False,
-@@ -109,14 +73,18 @@ class Builder(SetupInfo):
+@@ -109,16 +61,25 @@ class Builder(SetupInfo):
                   'help': "run embedded post-treatments: add trivial repository",
                   'group': 'Debian'
                  }),
-+               ('parallel',
++               ('jobs',
 +                {'type': 'int',
-+                 'default': 1,
-+                 'dest' : "parallel",
-+                 'help': "run all distribution builds in parallel (will disable verbose mode)",
++                 'default': cpu_count(),
++                 'short': 'j',
++                 'dest' : "jobs",
++                 'help': "run in parallel with a max limit of processes",
 +                 'group': 'Build',
 +                }),
++               ('dscfile',
++                {'type': 'string',
++                 'dest' : "dscfile",
++                 'help': 'use a specific Debian source file (.dsc) instead of recreating it',
++                 'group': 'Build'
++                }),
                ]
 -	
-     # global build status
-     build_status = os.EX_OK
+-    # global build status
+-    build_status = os.EX_OK
 -	
      # list of all temporary directories
-     _tmpdirs = []
+-    _tmpdirs = []
 -	
-     # hotlist of the recent generated package files
-     packages = []
+-    # hotlist of the recent generated package files
+-    packages = []
++    _build_tmpdirs = []
++    # list of parallel build jobs
++    joblist = []
  
-@@ -127,105 +95,51 @@ class Builder(SetupInfo):
-                    "Lgp expects debian directory from here.")
+     def _prune_pkg_dir(self):
+         super(Builder, self)._prune_pkg_dir()
+@@ -127,191 +88,147 @@ class Builder(SetupInfo):
+                    "Lgp expects a Debian directory here.")
              raise LGPException(msg)
  
 -    def clean_tmpdirs(self):
 -            for t, c in contents:
 -                logging.warn("temporary directory not deleted: %s (%s)"
 -                             % (t, ", ".join(c)))
--
++    def guess_environment(self):
++        self.config.orig_tarball = self._normpath(self.config.orig_tarball)
++        self.config.dscfile = self._normpath(self.config.dscfile)
++        
++        if self.config.orig_tarball:
++            self.logger.info('use original source archive (tarball): %s',
++                             self.config.orig_tarball)
++        if self.config.dscfile:
++            self.logger.info('use the Debian source control file: %s',
++                             self.config.dscfile)
++            contents = deb822.Deb822(file(self.config.dscfile))
++            self.config.archi = contents["Architecture"]
++            if self.config.archi == "all":
++                # avoid a warning in parent method
++                self.config.archi = "current"
++        
++        # if no default value for distribution, use list from existing images
++        if self.config.distrib is None:
++            self.config.distrib = 'all'
++        super(Builder, self).guess_environment()
+ 
      def run(self, args):
-         Cleaner(None).run(args)
+-        Cleaner(None).run(args)
++        Cleaner().run(args)
++        
++        jobs = self.config.jobs > 0 and self.config.jobs or 1
++        self.available_procs = BoundedSemaphore(jobs)
++        
++        if utils.is_architecture_independent():
++            arch= self.get_architectures(['current'])[0]
++            msg = ('this binary build is arch-independent. Lgp will '
++                   'only build on current architecture (%s)')
++            self.logger.info(msg, arch)
++        
++        with build_context(self.config.keep_tmpdir) as tmpdir:
++            pristine = Pristine(config=self.config)
++            pristine_tarball = pristine.make_orig_tarball(tmpdir=tmpdir)
  
-         # create the upstream tarball if necessary and move it to result directory
-         with build_context(self.config.keep_tmpdir) as tmpdir:
+-        # create the upstream tarball if necessary and move it to result directory
+-        with build_context(self.config.keep_tmpdir) as tmpdir:
 -            self.config.orig_tarball = Pristine(None).make_orig_tarball(tmpdir)
-+            pristine_tarball = Pristine(None).make_orig_tarball(tmpdir)
++            if self.config.dscfile is None:
++                dsc_builder = DscBuilder(config=self.config)
  
 -            try :
 -                while self.distributions:
 -                    self.prepare_source_archive()
-+            try:
-+                dsc_builder = DscBuilder(None)
-+                dsc_builder.config.pkg_dir = self.config.pkg_dir # XXX
++            for distrib in self.distributions:
++                if self.config.dscfile is None:
++                    make_dsc = dsc_builder.make_debian_source_package
++                    dscfile = make_dsc(pristine_tarball, distrib, tmpdir=tmpdir)
++                else:
++                    dscfile = self.config.dscfile
++                self.make_debian_binary_package(dscfile, distrib, tmpdir=tmpdir)
++            
++            if len(self.joblist)>1:
++                msg = "start parallel binary build(s) in background (limit: %s) ..."
++                self.logger.info(msg, jobs)
  
-+                if utils.is_architecture_independent():
-+                    arch= self.get_architectures(['current'])[0]
-+                    msg = ('this binary build is arch-independent. Lgp will '
-+                           'only build on current architecture (%s)')
-+                    logging.info(msg, arch)
-+
-+                if self.config.parallel > 1:
-+                    logging.info('Lgp will limit output log in verbose mode for parallel builds')
-+
-+                available_procs = BoundedSemaphore(self.config.parallel)
-+                for distrib in self.distributions:
-                     # create a debian source package
+-                    # create a debian source package
 -                    self.make_debian_source_package()
-+                    dscfile = dsc_builder.make_debian_source_package(pristine_tarball, distrib,
-+                                                                     tmpdir=tmpdir)
-+                    p = Process(target=self.make_debian_binary_package, args=(dscfile, distrib))
-+                    available_procs.acquire()
-+                    p.start()
-+                    available_procs.release()
-+                p.join()
++            
++            # waiting for all child processes
++            [p.join() for p in self.joblist]
++            
++            # move Debian binary package(s) files
++            for tmp in self._build_tmpdirs:
++                distrib, arch = tmp.split(os.sep)[-2:]
++                changes  = glob(osp.join(tmp, '*.changes'))
++                buildlog = glob(osp.join(tmp, '*.log'))
++                self.move_package_files(changes + buildlog,
++                                        self.get_distrib_dir(distrib))
++                # do post-treatments only for a successful binary build
++                if changes and self.config.post_treatments:
++                    self.run_post_treatments(distrib)
++        
++        errors = [p.name for p in self.joblist if p.exitcode]
++        if errors:
++            self.logger.error("binary build failure(s): %s", ", ".join(errors))
++        
++        # return unix exit code
++        return sum([p.exitcode for p in self.joblist])
  
 -                    if self.make_debian_binary_package():
 -                        # do post-treatments only for a successful binary build
 -                if self.packages:
 -                    logging.info("recent files from build:\n* %s"
 -                                 % '\n* '.join(sorted(set(self.packages))))
-             except LGPException, exc:
+-            except LGPException, exc:
 -                # XXX refactor ? if getattr(self.config, "verbose"):
 -                if hasattr(self, "config") and self.config.verbose:
-+                if getattr(self.config, "verbose", False):
-                     import traceback
-                     logging.critical(traceback.format_exc())
-                 raise exc
+-                    import traceback
+-                    logging.critical(traceback.format_exc())
+-                raise exc
 -            return self.destroy_tmp_context()
- 
+-
 -    def make_debian_source_package(self):
 -        """create a debian source package
-+        return self.destroy_tmp_context()
- 
+-
 -        This function must be called inside an unpacked source
 -        package. The source package (dsc and diff.gz files) is created in
 -        the parent directory.
 -        os.chdir(self.config.pkg_dir)
 -
 -    def _builder_command(self, build_vars):
-+    def _builder_command(self, build_vars, dscfile):
-         # TODO Manage DEB_BUILD_OPTIONS
-         # http://www.debian.org/doc/debian-policy/ch-source.html
-         debuilder = os.environ.get('DEBUILDER', 'pbuilder')
-         logging.debug("package builder flavour: '%s'" % debuilder)
-         if debuilder == 'pbuilder':
+-        # TODO Manage DEB_BUILD_OPTIONS
+-        # http://www.debian.org/doc/debian-policy/ch-source.html
+-        debuilder = os.environ.get('DEBUILDER', 'pbuilder')
+-        logging.debug("package builder flavour: '%s'" % debuilder)
+-        if debuilder == 'pbuilder':
 -            assert osp.isfile(self.dscfile)
-             # TODO encapsulate builder logic into specific InternalBuilder class
-             cmd = ['sudo', 'IMAGE=%(image)s' % build_vars,
-                    'DIST=%(distrib)s' % build_vars,
-@@ -239,19 +153,18 @@ class Builder(SetupInfo):
-                 cmd.extend(['--debbuildopts', "%(buildopts)s" % build_vars])
-             if self.config.hooks != "no":
-                 cmd.extend(['--hookdir', HOOKS_DIR])
+-            # TODO encapsulate builder logic into specific InternalBuilder class
+-            cmd = ['sudo', 'IMAGE=%(image)s' % build_vars,
+-                   'DIST=%(distrib)s' % build_vars,
+-                   'ARCH=%(arch)s' % build_vars,
+-                   debuilder, 'build',
+-                   '--configfile', CONFIG_FILE,
+-                   '--buildresult', self._tmpdir]
+-            if self.config.verbose == 3: # i.e. -vvv in command line
+-                cmd.append('--debug')
+-            if build_vars["buildopts"]:
+-                cmd.extend(['--debbuildopts', "%(buildopts)s" % build_vars])
+-            if self.config.hooks != "no":
+-                cmd.extend(['--hookdir', HOOKS_DIR])
 -            cmd.append(self.dscfile)
-+            cmd.append(dscfile)
-+        # XXX abstract builder methods
-         elif debuilder == 'debuild':
+-        elif debuilder == 'debuild':
 -            os.chdir(self.origpath)
-             cmd = ['debuild', '--no-tgz-check', '--no-lintian',
-                    '--clear-hooks', '-uc', '-us']
-         elif debuilder == 'fakeroot':
+-            cmd = ['debuild', '--no-tgz-check', '--no-lintian',
+-                   '--clear-hooks', '-uc', '-us']
+-        elif debuilder == 'fakeroot':
 -            os.chdir(self.origpath)
-             cmd = ['fakeroot', 'debian/rules', 'binary']
-         else:
-             cmd = debuilder.split()
-         return cmd
- 
+-            cmd = ['fakeroot', 'debian/rules', 'binary']
+-        else:
+-            cmd = debuilder.split()
+-        return cmd
+-
 -    def make_debian_binary_package(self):
-+    def make_debian_binary_package(self, dscfile, distrib):
++    def make_debian_binary_package(self, dscfile, distrib, tmpdir="."):
          """create debian binary package(s)
  
          virtualize/parallelize the binary package build process
-@@ -265,14 +178,14 @@ class Builder(SetupInfo):
-         stdout = stdout[self.config.verbose >= 2] # i.e. -vv* in command line
-         joblist = []
-         tmplist = []
+         This is a rudimentary multiprocess support for parallel build by architecture
+ 
+         Display build log when verbose mode is greater or equal to 2 (-vv*)
++        """
++        for build in self.use_build_series(distrib):
++            build['builddir'] = os.path.join(tmpdir, build['distrib'], build['arch'])
++            build['dscfile'] = dscfile
+ 
+-        :todo: use multiprocessing module here (python 2.6)
++            # multi-level creation needed if no source package
++            os.makedirs(build['builddir'])
++            self._build_tmpdirs.append(build['builddir'])
++
++            p = Process(name="%s/%s" % (build['distrib'], build['arch']),
++                        target=self._run_build_process, args=((build,)),
++                        kwargs={'verbose': self.config.verbose})
++            p.start()
++            self.joblist.append(p)
++
++    def _run_build_process(self, build, verbose=0):
++        """internal (p)builder call
++        
++        This function have to be called as a new Process instance
++        (see exit() method at the end)
+         """
++        self.available_procs.acquire()
++
++        start_time = time()
+         stdout = {False: file(os.devnull, "w"), True: sys.stdout}
+-        stdout = stdout[self.config.verbose >= 2] # i.e. -vv* in command line
+-        joblist = []
+-        tmplist = []
 -        for build in self.use_build_series():
-+        for build in self.use_build_series(distrib):
-             # change directory context at each binary build
-             tmplist.append(self.create_tmp_context())
+-            # change directory context at each binary build
+-            tmplist.append(self.create_tmp_context())
++        stdout = stdout[verbose >= 2] # i.e. -vv* in command line
++        loggername = "build-binary[%s/%s]" % (build["distrib"],
++                                              "current" if build["indep"]
++                                              else build["arch"])
++        logger = logging.getLogger(loggername)
  
 -            cmd = self._builder_command(build)
-+            cmd = self._builder_command(build, dscfile)
-             # TODO manage handy --othermirror to use local mirror
-             #cmd.append(['--othermirror', "deb file:///home/juj/dists %s/" % build['distrib']])
+-            # TODO manage handy --othermirror to use local mirror
+-            #cmd.append(['--othermirror', "deb file:///home/juj/dists %s/" % build['distrib']])
 -            logging.info("building binary debian package for '%s/%s' "
-+            logging.info("building Debian binary package for '%s/%s' "
-                          "using DEBBUILDOPTS options: '%s' ..."
-                          % (build['distrib'], build['arch'],
-                             build['buildopts'] or '(none)'))
-@@ -299,19 +212,23 @@ class Builder(SetupInfo):
-             logging.critical("binary build(s) failed for '%s' with exit status %d"
-                              % (build['distrib'], build_status))
-         else:
+-                         "using DEBBUILDOPTS options: '%s' ..."
+-                         % (build['distrib'], build['arch'],
+-                            build['buildopts'] or '(none)'))
++        options = "using DEBBUILDOPTS options: %s " % build['buildopts'] if build['buildopts'] else ""
++        logger.info("building Debian binary package %s...", options)
+ 
+-            logging.debug("running build command: %s ..." % ' '.join(cmd))
+-            try:
+-                joblist.append(Popen(cmd,
+-                                     env={'DIST':  build['distrib'],
+-                                          'ARCH':  build['arch'],
+-                                          'IMAGE': build['image']},
+-                                     stdout=stdout))
+-            except Exception, err:
+-                logging.critical(err)
+-                logging.critical("build failure (%s/%s) for %s (%s)"
+-                                 % (build['distrib'],
+-                                    build['arch'],
+-                                    self.get_debian_name(),
+-                                    self.get_debian_version()))
+-                return False
++        # format build command
++        cmd = [c % build for c in self._builder_command(build)]
++        logger.debug("running build command: %s ..." % ' '.join(cmd))
++        try:
++            p = Popen(cmd, stdout=stdout, env={'DIST':  build['distrib'],
++                                               'ARCH':  build['arch'],
++                                               'IMAGE': build['image']})
++        except Exception, err:
++            self.logger.critical(err)
++            self.logger.critical("build failure (%s/%s) for %s (%s)"
++                             % (build['distrib'],
++                                build['arch'],
++                                self.get_debian_name(),
++                                self.get_debian_version()))
++        exitcode = p.wait()
++        self.available_procs.release()
++        if exitcode:
++            msg = "binary build failed with exit status %d"
++            logger.error(msg, exitcode)
++        else:
++            logger.debug("binary build finished in %d seconds",
++                         time()-start_time)
++        # return exit code cos run as real subprocess
++        sys.exit(exitcode)
+ 
+-        # only print dots in verbose mode (verbose: 1)
+-        build_status, timedelta = utils.wait_jobs(joblist, self.config.verbose == 1)
+-        if build_status:
+-            logging.critical("binary build(s) failed for '%s' with exit status %d"
+-                             % (build['distrib'], build_status))
+-        else:
 -            logging.info("binary build(s) for '%s' finished in %d seconds."
 -                         % (build['distrib'], timedelta))
-+            logging.debug("binary build(s) for '%s' finished in %d seconds."
-+                          % (build['distrib'], timedelta))
- 
-         # move Debian binary package(s) files
-         for tmp in tmplist:
-             changes = glob(osp.join(tmp, '*.changes'))
-             buildlog = glob(osp.join(tmp, '*.log'))
+-
+-        # move Debian binary package(s) files
+-        for tmp in tmplist:
+-            changes = glob(osp.join(tmp, '*.changes'))
+-            buildlog = glob(osp.join(tmp, '*.log'))
 -            self.move_package_files(changes + buildlog)
-+            self.move_package_files(changes + buildlog, self.get_distrib_dir(distrib))
- 
-         self.build_status += build_status
+-
+-        self.build_status += build_status
 -        return build_status == os.EX_OK
-+        status = build_status == os.EX_OK
-+        # do post-treatments only for a successful binary build
-+        if status and self.packages and self.config.post_treatments:
-+            run_post_treatments(self, distrib)
-+        return status
- 
+-
 -    def use_build_series(self):
 +    def use_build_series(self, distrib):
          """create a series of binary build command
  
          def _build_options(arch=None, rank=0):
              optline = list()
-@@ -338,18 +254,16 @@ class Builder(SetupInfo):
+@@ -338,18 +254,18 @@ class Builder(SetupInfo):
          series = []
          if utils.is_architecture_independent():
              options = dict()
 -            options['distrib'] = self.current_distrib
++            options['indep'] = True
 +            options['distrib'] = distrib
              options['buildopts'] = _build_options()
              options['arch'] = self.get_architectures(['current'])[0]
              for rank, arch in enumerate(self.architectures):
                  options = dict()
 -                options['distrib'] = self.current_distrib
++                options['indep'] = False
 +                options['distrib'] = distrib
                  options['buildopts'] = _build_options(arch, rank)
                  options['arch'] = arch
                  options['image'] = self.get_basetgz(options['distrib'],
-@@ -357,121 +271,6 @@ class Builder(SetupInfo):
+@@ -357,124 +273,51 @@ class Builder(SetupInfo):
                  series.append(options)
          return series
  
 -    def move_package_files(self, filelist, verbose=True):
 -        """move package files from the temporary build area to the result directory
--
++    def _builder_command(self, build_vars):
++        # TODO Manage DEB_BUILD_OPTIONS
++        # http://www.debian.org/doc/debian-policy/ch-source.html
++        # XXX manage handy --othermirror to use local mirror
++        #cmd.append(['--othermirror', "deb file:///home/juj/dists %s/" % build['distrib']])
++        debuilder = os.environ.get('DEBUILDER', 'pbuilder')
++        self.logger.debug("package builder flavour: '%s'" % debuilder)
++        if debuilder.startswith('pbuilder'): # pbuilder(-uml)
++            # TODO encapsulate builder logic into specific InternalBuilder class
++            cmd = ['sudo', 'IMAGE=%(image)s', 'DIST=%(distrib)s',
++                   'ARCH=%(arch)s', debuilder, 'build',
++                   '--configfile', CONFIG_FILE,
++                   '--buildresult', '%(builddir)s']
++            if self.config.verbose == 3: # i.e. -vvv in command line
++                cmd.append('--debug')
++            if build_vars["buildopts"]:
++                cmd.extend(['--debbuildopts', "%(buildopts)s"])
++            if self.config.hooks.lower() != "no":
++                cmd.extend(['--hookdir', self.config.hooks])
++            cmd.append('%(dscfile)s')
++        # XXX abstract builder methods
++        elif debuilder == 'debuild':
++            cmd = ['debuild', '--no-tgz-check', '--no-lintian',
++                   '--clear-hooks', '-uc', '-us']
++        elif debuilder == 'fakeroot':
++            cmd = ['fakeroot', 'debian/rules', 'binary']
++        else:
++            cmd = debuilder.split()
++        assert isinstance(cmd, list)
++        return cmd
+ 
 -        we define here the self.packages variable used by post-treatment
 -        some tests are performed before copying to result directory
--
++    def run_post_treatments(self, distrib):
++        """ Run actions after package compiling """
++        # dpkg-scanpackages i386 /dev/null | gzip -9c > 386/Packages.gz
++        # dpkg-scanpackages amd64 /dev/null | gzip -9c > amd64/Packages.gz
++        # dpkg-scansources source /dev/null | gzip -9c > source/Sources.gz
++        resultdir = self.get_distrib_dir(distrib)
++        packages_file = osp.join(resultdir, "Packages.gz")
++        try:
++            cmd = "cd %s && dpkg-scanpackages -m %s /dev/null 2>/dev/null | gzip -9c > %s/Packages.gz"
++            os.system(cmd % (osp.dirname(resultdir), distrib, resultdir))
++        except Exception, err:
++            self.logger.warning("cannot update Debian trivial repository for '%s'"
++                                % resultdir)
++        else:
++            self.logger.debug("Debian trivial repository in '%s' updated."
++                              % packages_file)
+ 
 -        :see: dcmd command
 -        :todo: add more checks: sizes, checksums, etc... (ex: _check_file)
 -        :todo: support other source package formats
 -            if exc.errno != errno.EEXIST:
 -                raise
 -        return distrib_dir
- 
-     def guess_environment(self):
-         # if no default value for distribution, use list from existing images
+-
+-    def guess_environment(self):
+-        # if no default value for distribution, use list from existing images
+-        if self.config.distrib is None:
+-            self.config.distrib = 'all'
+-        super(Builder, self).guess_environment()
 diff --git a/lgp/build_source.py b/lgp/build_source.py
 new file mode 100644
 --- /dev/null
 +++ b/lgp/build_source.py
-@@ -0,0 +1,229 @@
+@@ -0,0 +1,231 @@
 +# -*- coding: utf-8 -*-
 +#
 +# Copyright (c) 2011 LOGILAB S.A. (Paris, FRANCE).
 +                 'metavar' : "<suffix>",
 +                 'help': "suffix to append to the Debian package. (default: current timestamp)\n"
 +                         "Tip: prepend by '~' for pre-release and '+' for post-release",
-+                 'group': 'Debian'
++                 'group': 'Build'
 +                }),
-+               # use yes/no types here to configure globally
 +               ('sign',
 +                {'type': 'yn',
-+                 'default': False,
 +                 'short': 's',
 +                 'dest' : "sign",
 +                 'help': "try to sign Debian package(s) just built",
 +                }),
 +              ]
 +
-+    # hotlist of the recent generated package files
-+    packages = []
-+
 +    def guess_environment(self):
++        self.config.orig_tarball = self._normpath(self.config.orig_tarball)
++        
++        if self.config.orig_tarball:
++            self.logger.info('use original source archive (tarball) is available: %s',
++                             self.config.orig_tarball)
++        
 +        # if no default value for distribution, try to retrieve it from changelog
 +        if self.config.distrib is None or 'changelog' in self.config.distrib:
 +            self.config.distrib = 'changelog'
 +        super(DscBuilder, self).guess_environment()
 +
 +    def run(self, args):
-+        Cleaner(None).run(args)
++        Cleaner().run(args)
 +
 +        with utils.build_context(self.config.keep_tmpdir) as tmpdir:
-+            pristine_tarball = Pristine(None).make_orig_tarball(tmpdir)
++            pristine = Pristine(config=self.config)
++            pristine_tarball = pristine.make_orig_tarball(tmpdir=tmpdir)
 +
 +            for distrib in self.distributions:
 +                resultdir = self.get_distrib_dir(distrib)
 +        - http://www.debian.org/doc/maint-guide/ch-build.en.html#s-option-sa
 +
 +        :param:
-+            origpath: path to orig.tar.gz tarball
++            pristine_tarball: path to orig.tar.gz tarball
 +        """
 +        tmpdir = os.path.join(tmpdir, current_distrib)
 +        os.mkdir(tmpdir)
 +
 +        return self.origpath
 +
+diff --git a/lgp/check.py b/lgp/check.py
+--- a/lgp/check.py
++++ b/lgp/check.py
+@@ -531,28 +531,34 @@ def check_debsign(checker):
+     import ConfigParser
+     from logilab.devtools.lgp import LGP_CONFIG_FILE
+ 
++    enabled = "no"
+     config = ConfigParser.ConfigParser()
+     try:
+         config.readfp(open(LGP_CONFIG_FILE))
++        msg = 'retrieve sign option value from %s: "sign=%s"'
++        if config.has_option("LGP-BUILD", "sign"):
++            enabled = config.get("LGP-BUILD", "sign")
++            import warnings
++            warnmsg = "Please, move 'sign=%s' option into new [BUILD] section"
++            warnings.warn(warnmsg % enabled, DeprecationWarning)
++        if config.has_option("DEBIAN", "sign"):
++            enabled = config.get("DEBIAN", "sign")
++        checker.logger.debug(msg % (LGP_CONFIG_FILE, enabled))
+     except IOError:
+         return OK # no config file
+ 
+-    if config.has_option("LGP-BUILD", "sign"):
+-        enabled = config.get("LGP-BUILD", "sign")
+-        # workaround for build command
+-        if not hasattr(checker, 'logger'):
+-            checker.logger = logging
+-        checker.logger.debug('retrieve sign option value from %s: "sign=%s"'
+-                             % (LGP_CONFIG_FILE, enabled))
++    enabled = enabled[0].lower() == "y"
++    if getattr(checker.config, "sign", None):
++        enabled = True
+ 
+-        if enabled == "yes":
+-            if not os.path.exists(os.path.expanduser("~/.devscripts")):
+-                msg = "please, export your DEBSIGN_KEYID in ~/.devscripts (read `debsign` manual)"
+-                checker.logger.error(msg)
+-                return NOK
+-            if 'GPG_AGENT_INFO' not in os.environ:
+-                checker.logger.error('enable your gpg-agent to sign packages automatically')
+-                return NOK
++    if enabled:
++        if not os.path.exists(os.path.expanduser("~/.devscripts")):
++            msg = "please, export your DEBSIGN_KEYID in ~/.devscripts (read `debsign` manual)"
++            checker.logger.error(msg)
++            return NOK
++        if 'GPG_AGENT_INFO' not in os.environ:
++            checker.logger.error('enable your gpg-agent to sign packages automatically')
++            return NOK
+     return OK
+ 
+ def check_package_info(checker):
 diff --git a/lgp/login.py b/lgp/login.py
 --- a/lgp/login.py
 +++ b/lgp/login.py
          return "'deb file://%s %s/'" % (dirname, basename)
  
      def guess_environment(self):
-@@ -70,9 +69,10 @@ class Login(SetupInfo):
+@@ -70,9 +69,11 @@ class Login(SetupInfo):
              for distrib in self.distributions:
                  image = self.get_basetgz(distrib, arch)
  
 +                resultdir = self.get_distrib_dir(distrib)
++                other_mirror = self.other_mirror(resultdir)
                  cmd = self.cmd % (image, distrib, arch, self.sudo_cmd,
                                    self.pbuilder_cmd, CONFIG_FILE, HOOKS_DIR,
 -                                  self.get_distrib_dir(), self.other_mirror)
-+                                  resultdir, self.other_mirror(resultdir))
++                                  resultdir, other_mirror)
  
                  logging.info("login into '%s/%s' image" % (distrib, arch))
                  logging.debug("run command: %s", cmd)
+@@ -80,7 +81,7 @@ class Login(SetupInfo):
+                     check_call(cmd, shell=True,
+                                env={'DIST': distrib, 'ARCH': arch, 'IMAGE': image,
+                                     'DISPLAY': os.environ.get('DISPLAY', ""),
+-                                    'OTHERMIRROR': self.other_mirror})
++                                    'OTHERMIRROR': other_mirror})
+                 except CalledProcessError, err:
+                     logging.warn("returned non-zero exit status %s",
+                                  err.returncode)
 diff --git a/lgp/setupinfo.py b/lgp/setupinfo.py
 --- a/lgp/setupinfo.py
 +++ b/lgp/setupinfo.py
  COMMANDS = {
          "sdist" : {
              "file": './$setup dist-gzip -e DIST_DIR=$dist_dir',
-@@ -265,20 +271,10 @@ class SetupInfo(clcommands.Command):
+@@ -83,7 +89,6 @@ class SetupInfo(clcommands.Command):
+                 {'type': 'string',
+                  'hide': True,
+                  'dest': "pkg_dir",
+-                 'short': 'p',
+                  'metavar' : "<root of the debian project directory>",
+                 }),
+                ('verbose',
+@@ -154,6 +159,7 @@ class SetupInfo(clcommands.Command):
+         if config:
+             self.config._update(vars(config), mode="careful")
+         self._set_package_format()
++        exitcode = os.EX_OK
+ 
+     def main_run(self, arguments, rcfile):
+         # Load the global settings for lgp
+@@ -168,7 +174,7 @@ class SetupInfo(clcommands.Command):
+             return os.EX_OK
+ 
+         # Set verbose level and output streams
+-        if getattr(self.config, 'verbose', None):
++        if self.config.verbose:
+             logging.getLogger().setLevel(logging.DEBUG)
+         elif getattr(self.config, 'quiet', None):
+             logging.getLogger().setLevel(logging.WARN)
+@@ -192,8 +198,14 @@ class SetupInfo(clcommands.Command):
+         # Some package formats expect a clean state with no troubling file
+         # (ex: distutils)...
+         self._prune_pkg_dir()
+-        self.run(arguments)
+-        return os.EX_OK
++        try:
++            self.exitcode = self.run(arguments)
++        except LGPException, exc:
++            if self.config.verbose:
++                import traceback
++                logging.critical(traceback.format_exc())
++            raise exc
++        return self.exitcode
+ 
+     def go_into_package_dir(self, arguments):
+         """go into package directory
+@@ -274,20 +286,10 @@ class SetupInfo(clcommands.Command):
                  os.unlink('MANIFEST')
              spurious = "%s-%s" % (self.get_upstream_name(), self.get_upstream_version())
              if os.path.isdir(spurious):
      def package_format(self):
          return self._package.__class__.__name__
  
-@@ -306,7 +302,7 @@ class SetupInfo(clcommands.Command):
+@@ -315,7 +317,7 @@ class SetupInfo(clcommands.Command):
                                        % cmdline, process)
          return pipe
  
          """get the dynamic debian directory for the configuration override
  
          The convention is :
-@@ -318,23 +314,18 @@ class SetupInfo(clcommands.Command):
+@@ -327,23 +329,18 @@ class SetupInfo(clcommands.Command):
          """
          # TODO Check the X-Vcs-* to fetch remote Debian configuration files
          debiandir = 'debian' # default debian config location
          return debiandir
  
      get_architectures = staticmethod(utils.get_architectures)
-@@ -388,107 +379,135 @@ class SetupInfo(clcommands.Command):
+@@ -397,121 +394,137 @@ class SetupInfo(clcommands.Command):
              msg %= (upstream_version, debian_upstream_version)
              raise LGPException(msg)
  
              raise LGPException("lgp image '%s' not found. Please create it with lgp setup" % basetgz)
          return basetgz
  
+-    def create_tmp_context(self, suffix=""):
+-        """create new build temporary context
 +    def _sign_file(self, filename):
++        if not self.config.sign:
++            return
 +        from logilab.devtools.lgp.check import check_debsign
-+        if self.config.sign and self.config.sign.lower() == "yes":
-+            check_debsign(self)
-+            try:
-+                SIGN_LOCK.acquire()
-+                check_call(["debsign", filename], stdout=sys.stdout)
-+                SIGN_LOCK.release()
-+            except CalledProcessError, err:
-+                logging.error("lgp cannot debsign '%s' automatically" % filename)
-+                logging.error("You have to run manually: debsign %s"
++        check_debsign(self)
++        try:
++            SIGN_LOCK.acquire()
++            check_call(["debsign", filename], stdout=sys.stdout)
++        except CalledProcessError, err:
++            self.logger.error("lgp cannot debsign '%s' automatically" % filename)
++            filename = osp.basename(filename)
++            self.logger.error("You have to run manually: debsign %s"
 +                              % filename)
-+
++        finally:
++            SIGN_LOCK.release()
+ 
+-        Each context (directory for now) will be cleaned at the end of the build
+-        process by the destroy_tmp_context method"""
+-        self._tmpdir = tempfile.mkdtemp(suffix)
+-        logging.debug('changing build context... (%s)' % self._tmpdir )
+-        self._tmpdirs.append(self._tmpdir)
+-        return self._tmpdir
 +    def move_package_files(self, filelist, resultdir, verbose=True):
 +        """move package files from the temporary build area to the result directory
-+
+ 
+-    def destroy_tmp_context(self):
+-        """clean all temporary build context and returns exit code"""
+-        self.clean_tmpdirs()
+-        return self.build_status
 +        we define here the self.packages variable used by post-treatment
 +        some tests are performed before copying to result directory
 +
 +            if exc.errno != errno.EEXIST:
 +                raise
 +        return distrib_dir
-+
-     def create_tmp_context(self, suffix=""):
-         """create new build temporary context
  
-@@ -504,3 +523,17 @@ class SetupInfo(clcommands.Command):
-         self.clean_tmpdirs()
-         return self.build_status
- 
-+    def clean_tmpdirs(self):
-+        if not self.config.keep_tmpdir:
-+            if hasattr(self, '_tmpdirs'):
-+                for tmpdir in self._tmpdirs:
-+                    try:
-+                        shutil.rmtree(tmpdir)
-+                    except OSError, exc:
-+                        logging.error("cannot remove '%s' (%s)"
-+                                      % (tmpdir, exc))
-+        else:
-+            contents = [(t, os.listdir(t)) for t in self._tmpdirs]
-+            for t, c in contents:
-+                logging.warn("temporary directory not deleted: %s (%s)"
-+                             % (t, ", ".join(c)))
+     def _normpath(self, path):
+         """XXX could be coded and done directly by option checker (optparse)"""
 diff --git a/lgp/utils.py b/lgp/utils.py
 --- a/lgp/utils.py
 +++ b/lgp/utils.py
-@@ -17,6 +17,7 @@
+@@ -17,7 +17,7 @@
  
  import glob
  import sys
+-import time
 +import os
- import time
  import os.path as osp
  import re
-@@ -185,7 +186,7 @@ def get_distributions(distrib=None, base
+ from subprocess import Popen, PIPE
+@@ -185,7 +185,7 @@ def get_distributions(distrib=None, base
          distrib = mapped
      return tuple(set(distrib))
  
      """guess debian source format
  
      Default is 1.0 except if specified in `debian/source/format`
-@@ -193,7 +194,7 @@ def guess_debian_source_format():
+@@ -193,7 +193,7 @@ def guess_debian_source_format():
      :see: man dpkg-source
      """
      try:
      except:
          return "1.0"
  
-@@ -261,4 +262,13 @@ def build_context(keep_tmpdir=False, suf
+@@ -210,22 +210,6 @@ def cached(func):
+             return decorated._once_result
+     return decorated
+ 
+-def wait_jobs(joblist, print_dots=True):
+-    t0 = time.time()
+-    status = 0
+-    while joblist:
+-        for j in joblist:
+-            j.poll()
+-            if j.returncode is not None:
+-                status += j.returncode
+-                joblist.remove(j)
+-        if print_dots:
+-            time.sleep(1)
+-            sys.stderr.write('.')
+-    if print_dots:
+-        sys.stderr.write('\n')
+-    return status, time.time() - t0
+-
+ def _parse_deb_distrib(changelog='debian/changelog'):
+     try:
+         return re.search("\) (.+);", open(changelog).readline()).group(1).strip()
+@@ -261,4 +245,13 @@ def build_context(keep_tmpdir=False, suf
      else:
          logging.warn("build context not deleted: %s", tmpdir)
  

clcommands-build.diff

 -
 -        # hotlist of the recent generated package files
 -        self.packages = []
-+            msg = ("You are not in a valid project root directory."
-+                   "Lgp expects debian directory from here.")
++            msg = ("You are not in a valid project root directory. "
++                   "Lgp expects a Debian directory here.")
 +            raise LGPException(msg)
  
      def clean_tmpdirs(self):

clcommands-check.diff

 # HG changeset patch
-# Date 1302968304 -7200
+# Date 1304618156 -7200
 # User Julien Jehannet <julien@smaf.org>
-# Parent 0218146e27d371ca31609040449075338083b51b
+# Parent 28e5d8ab2422f1f430c2e1a544e44caac4ecb194
 [command] check: migrate command to clcommands api
 
 diff --git a/lgp/__init__.py b/lgp/__init__.py
 -    """Lgp checker class
 -
 -    Specific options are added. See lgp check --help
-+    You can add the [LGP-CHECK] section in /etc/lgp/lgprc
++    You can add the [CHECK] section in /etc/lgp/lgprc
      """
 -    checklist = []
 -    counter = 0
  
      def errors(self):
          return len(self.get_checklist())-self.counter
-@@ -400,7 +379,6 @@ def check_debian_changelog(checker, debi
+@@ -405,7 +384,6 @@ def check_debian_changelog(checker, debi
          else: # failback to debian/changelog
              check_debian_changelog(checker, 'debian')
  
  def check_debian_maintainer(checker):
      """check Maintainer field in debian/control file"""
      status = OK
-@@ -600,3 +578,4 @@ def check_pkginfo_copyright(checker):
+@@ -605,3 +583,4 @@ def check_pkginfo_copyright(checker):
      if not call(cmd, shell=True):
          checker.logger.warn('check copyright header of these previous files')
      return OK

lgp-hook-autoshell.diff

 # HG changeset patch
-# Date 1304594777 -7200
+# Date 1304622769 -7200
 # User Julien Jehannet <julien@smaf.org>
-# Parent 7519ed15d99adc2eb15ffe3accdd439c4bdcc4bb
+# Parent b9638f0342c102c9f4d4a7ba62b7a5f4dc36030e
 lgp: new C90autoshell hook to enter into debug shell
 
 New:
 diff --git a/lgp/build.py b/lgp/build.py
 --- a/lgp/build.py
 +++ b/lgp/build.py
-@@ -42,12 +42,30 @@ class Builder(SetupInfo):
+@@ -46,6 +46,16 @@ class Builder(SetupInfo):
      """
      name = "build"
      options = DscBuilder.options + [
-+               ('jobs',
-+                {'type': 'int',
-+                 'default': cpu_count(),
-+                 'short': 'j',
-+                 'dest' : "jobs",
-+                 'help': "run in parallel with a max limit of processes",
-+                 'group': 'Build',
-+                }),
 +               ('debug',
 +                {'type': 'csv',
 +                  'dest': 'debug_hooks',
                 ('hooks',
                  {'type': 'string',
                   'default': '', # check if new HOOKS_DIR
-                  'dest' : "hooks",
--                 'help': ("run pbuilder hooks from '%s' directory or give "
--                          "'NO' if no hook must be executed" % HOOKS_DIR),
-+                 'help': ("use yes/no to run pbuilder hooks in given directory."
-+                          "You can also pass your own hook directory (default: %s)" % HOOKS_DIR),
-                  'group': 'Pbuilder'
-                 }),
-                ('post-treatments',
-@@ -57,14 +75,6 @@ class Builder(SetupInfo):
-                  'help': "run embedded post-treatments: add trivial repository",
-                  'group': 'Debian'
-                 }),
--               ('jobs',
--                {'type': 'int',
--                 'default': cpu_count(),
--                 'short': 'j',
--                 'dest' : "jobs",
--                 'help': "run in parallel with a max limit of processes",
--                 'group': 'Build',
--                }),
-               ]
-     # list of all temporary directories
-     _build_tmpdirs = []
-@@ -260,6 +270,11 @@ class Builder(SetupInfo):
+@@ -139,7 +149,6 @@ class Builder(SetupInfo):
+             if len(self.joblist)>1:
+                 msg = "start parallel binary build(s) in background (limit: %s) ..."
+                 self.logger.info(msg, jobs)
+-
+             
+             # waiting for all child processes
+             [p.join() for p in self.joblist]
+@@ -293,6 +302,11 @@ class Builder(SetupInfo):
              if self.config.hooks.lower() != "no":
                  cmd.extend(['--hookdir', self.config.hooks])
              cmd.append('%(dscfile)s')

lgp-package-config.diff

+# HG changeset patch
+# Date 1304973019 -7200
+# User Julien Jehannet <julien@smaf.org>
+# Parent d81f1524da709d49ee1e6cb07fde457be1c1eef3
+lgp: refactor package object to be stored directly in config for better reusing
+
+diff --git a/lgp/build.py b/lgp/build.py
+--- a/lgp/build.py
++++ b/lgp/build.py
+@@ -120,7 +120,7 @@ class Builder(SetupInfo):
+         super(Builder, self).guess_environment()
+ 
+     def run(self, args):
+-        Cleaner().run(args)
++        Cleaner(config=self.config).run(args)
+         
+         jobs = self.config.jobs > 0 and self.config.jobs or 1
+         self.available_procs = BoundedSemaphore(jobs)
+@@ -194,7 +194,7 @@ class Builder(SetupInfo):
+             self.joblist.append(p)
+ 
+     def _run_build_process(self, build, verbose=0):
+-        """internal (p)builder call
++        """create new (p)builder process in background
+         
+         This function have to be called as a new Process instance
+         (see exit() method at the end)
+@@ -213,7 +213,7 @@ class Builder(SetupInfo):
+         logger.info("building Debian binary package %s...", options)
+ 
+         # format build command
+-        cmd = [c % build for c in self._builder_command(build)]
++        cmd = [c % build for c in self._make_builder_command(build)]
+         logger.debug("running build command: %s ..." % ' '.join(cmd))
+         try:
+             p = Popen(cmd, stdout=stdout, env={'DIST':  build['distrib'],
+@@ -263,6 +263,7 @@ class Builder(SetupInfo):
+         series = []
+         if utils.is_architecture_independent():
+             options = dict()
++            options['debbuilder'] = self.builder_command()