← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad-buildd/update-chroot-python into lp:launchpad-buildd

 

Colin Watson has proposed merging lp:~cjwatson/launchpad-buildd/update-chroot-python into lp:launchpad-buildd.

Commit message:
Rewrite update-debian-chroot in Python, allowing it to use lpbuildd.util
and to have unit tests.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad-buildd/update-chroot-python/+merge/328222

I put the guts of the rewritten script in a new lpbuildd.target namespace to keep things organised.  My intent is that everything that runs in the target (chroot or eventually LXD) should end up in there.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad-buildd/update-chroot-python into lp:launchpad-buildd.
=== modified file 'Makefile'
--- Makefile	2017-07-28 10:39:50 +0000
+++ Makefile	2017-07-28 14:04:50 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 all: deb
@@ -22,16 +22,5 @@
 
 PYTHON=python
 check:
-	PYTHONPATH=$(CURDIR):$(PYTHONPATH) $(PYTHON) -m testtools.run -v \
-		   lpbuildd.pottery.tests.test_generate_translation_templates \
-		   lpbuildd.pottery.tests.test_intltool \
-		   lpbuildd.tests.test_binarypackage \
-		   lpbuildd.tests.test_buildd_slave \
-		   lpbuildd.tests.test_buildrecipe \
-		   lpbuildd.tests.test_check_implicit_pointer_functions \
-		   lpbuildd.tests.test_debian \
-		   lpbuildd.tests.test_harness \
-		   lpbuildd.tests.test_livefs \
-		   lpbuildd.tests.test_snap \
-		   lpbuildd.tests.test_sourcepackagerecipe \
-		   lpbuildd.tests.test_translationtemplatesbuildmanager
+	PYTHONPATH=$(CURDIR):$(PYTHONPATH) $(PYTHON) -m testtools.run \
+		discover -v

=== modified file 'bin/buildlivefs'
--- bin/buildlivefs	2017-07-25 22:11:19 +0000
+++ bin/buildlivefs	2017-07-28 14:04:50 +0000
@@ -1,5 +1,5 @@
 #! /usr/bin/python -u
-# Copyright 2013 Canonical Ltd.  This software is licensed under the
+# Copyright 2013-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """A script that builds a live file system."""
@@ -48,7 +48,8 @@
 
         :param args: the command and arguments to run.
         """
-        args = set_personality(self.options.arch, args)
+        args = set_personality(
+            args, self.options.arch, series=self.options.series)
         if echo:
             print("Running in chroot: %s" %
                   ' '.join("'%s'" % arg for arg in args))

=== modified file 'bin/buildsnap'
--- bin/buildsnap	2017-07-25 22:11:19 +0000
+++ bin/buildsnap	2017-07-28 14:04:50 +0000
@@ -1,5 +1,5 @@
 #! /usr/bin/python -u
-# Copyright 2015 Canonical Ltd.  This software is licensed under the
+# Copyright 2015-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """A script that builds a snap."""
@@ -58,7 +58,7 @@
         :param echo: if True, print the command before executing it.
         :param get_output: if True, return the output from the command.
         """
-        args = set_personality(self.options.arch, args)
+        args = set_personality(args, self.options.arch)
         if echo:
             print(
                 "Running in chroot: %s" % ' '.join(

=== modified file 'bin/update-debian-chroot'
--- bin/update-debian-chroot	2017-07-26 12:01:39 +0000
+++ bin/update-debian-chroot	2017-07-28 14:04:50 +0000
@@ -1,54 +1,35 @@
-#!/bin/sh
+#! /usr/bin/python -u
 #
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-# Buildd Slave tool to update a debian chroot
-
-# Expects build id as arg 1, makes build-id to contain the build
-
-# Needs SUDO to be set to a sudo instance for passwordless access
-
-SUDO=/usr/bin/sudo
-CHROOT=/usr/sbin/chroot
-APTGET=/usr/bin/apt-get
-BUILDID="$1"
-ARCHITECTURETAG="$2"
-ROOT=$HOME/build-$BUILDID/chroot-autobuild
-OPTIONS="-o DPkg::Options::=--force-confold"
-
-set -e
-
-exec 2>&1
-
-echo "Updating debian chroot for build $BUILDID"
-
-UNAME26=""
-case $SUITE in
-  hardy*|lucid*|maverick*|natty*|oneiric*|precise*)
-    if setarch --help | grep -q uname-2.6; then
-      UNAME26="--uname-2.6"
-    fi
-    ;;
-esac
-
-case $ARCHITECTURETAG in
-  armel|armhf|hppa|i386|lpia|mips|mipsel|powerpc|s390|sparc)
-    CHROOT="linux32 $UNAME26 $CHROOT"
-    ;;
-  alpha|amd64|arm64|hppa64|ia64|ppc64|ppc64el|s390x|sparc64|x32)
-    CHROOT="linux64 $UNAME26 $CHROOT"
-    ;;
-esac
-
-export LANG=C
-export DEBIAN_FRONTEND=noninteractive
-export TTY=unknown
-
-if ! $SUDO $CHROOT $ROOT $APTGET -uy update < /dev/null; then
-	echo "Waiting 15 seconds and trying again ..." >&2
-	sleep 15
-	$SUDO $CHROOT $ROOT $APTGET -uy update < /dev/null
-fi
-$SUDO $CHROOT $ROOT $APTGET $OPTIONS -uy --purge dist-upgrade < /dev/null
-
+"""Update a Debian-style chroot."""
+
+from __future__ import print_function
+
+__metaclass__ = type
+
+from argparse import ArgumentParser
+import sys
+
+from lpbuildd.target.chroot import ChrootUpdater
+
+
+def main():
+    parser = ArgumentParser(description="Update a Debian-style chroot.")
+    parser.add_argument(
+        "--series", metavar="SERIES", help="update chroot for series SERIES")
+    parser.add_argument(
+        "--arch", metavar="ARCH", help="update chroot for architecture ARCH")
+    parser.add_argument(
+        "build_id", metavar="ID", help="update chroot for build ID")
+    args = parser.parse_args()
+
+    print("Updating debian chroot for build %s" % args.build_id)
+    updater = ChrootUpdater(args.build_id, args.series, args.arch)
+    updater.update()
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())

=== modified file 'debian/changelog'
--- debian/changelog	2017-07-28 11:10:52 +0000
+++ debian/changelog	2017-07-28 14:04:50 +0000
@@ -6,6 +6,10 @@
     ppa:launchpad/ubuntu/ppa (and >= zesty).
   * Drop qemu emulation support.  It was quite unreliable, and we've had
     real hardware for a while.
+  * Remove most architecture hardcoding from lpbuildd.util, relying on
+    dpkg-architecture instead.
+  * Rewrite update-debian-chroot in Python, allowing it to use lpbuildd.util
+    and to have unit tests.
 
  -- Colin Watson <cjwatson@xxxxxxxxxx>  Tue, 25 Jul 2017 23:07:58 +0100
 

=== modified file 'debian/control'
--- debian/control	2017-07-28 13:22:05 +0000
+++ debian/control	2017-07-28 14:04:50 +0000
@@ -3,7 +3,11 @@
 Priority: extra
 Maintainer: Adam Conrad <adconrad@xxxxxxxxxx>
 Standards-Version: 3.9.5
+<<<<<<< TREE
 Build-Depends: debhelper (>= 7.0.50~), apt-utils, bzr, intltool, python, python-apt, python-debian, python-fixtures, python-testtools, python-twisted, python-txfixtures, python-zope.interface
+=======
+Build-Depends: debhelper (>= 7.0.50~), bzr, intltool, python, python-apt, python-debian, python-fixtures, python-mock, python-systemfixtures, python-testtools, python-twisted, python-txfixtures, python-zope.interface
+>>>>>>> MERGE-SOURCE
 
 Package: launchpad-buildd
 Section: misc

=== modified file 'lpbuildd/debian.py'
--- lpbuildd/debian.py	2017-04-26 12:24:32 +0000
+++ lpbuildd/debian.py	2017-07-28 14:04:50 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009, 2010 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 # Authors: Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>
@@ -52,7 +52,7 @@
 
     def initiate(self, files, chroot, extra_args):
         """Initiate a build with a given set of files and chroot."""
-
+        self.series = extra_args['series']
         self.arch_tag = extra_args.get('arch_tag', self._slave.getArch())
         self.sources_list = extra_args.get('archives')
         self.trusted_keys = extra_args.get('trusted_keys')
@@ -80,7 +80,10 @@
         """Perform the chroot upgrade."""
         self.runSubProcess(
             self._updatepath,
-            ["update-debian-chroot", self._buildid, self.arch_tag])
+            ["update-debian-chroot",
+             "--series", self.series,
+             "--arch", self.arch_tag,
+             self._buildid])
 
     def doRunBuild(self):
         """Run the main build process.

=== modified file 'lpbuildd/livefs.py'
--- lpbuildd/livefs.py	2015-05-11 05:39:25 +0000
+++ lpbuildd/livefs.py	2017-07-28 14:04:50 +0000
@@ -1,4 +1,4 @@
-# Copyright 2013 Canonical Ltd.  This software is licensed under the
+# Copyright 2013-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -41,7 +41,6 @@
         self.subarch = extra_args.get("subarch")
         self.project = extra_args["project"]
         self.subproject = extra_args.get("subproject")
-        self.series = extra_args["series"]
         self.pocket = extra_args["pocket"]
         self.datestamp = extra_args.get("datestamp")
         self.image_format = extra_args.get("image_format")

=== added directory 'lpbuildd/target'
=== added file 'lpbuildd/target/__init__.py'
=== added file 'lpbuildd/target/chroot.py'
--- lpbuildd/target/chroot.py	1970-01-01 00:00:00 +0000
+++ lpbuildd/target/chroot.py	2017-07-28 14:04:50 +0000
@@ -0,0 +1,62 @@
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+from __future__ import print_function
+
+__metaclass__ = type
+
+import os.path
+import subprocess
+import sys
+import time
+
+from lpbuildd.util import (
+    set_personality,
+    shell_escape,
+    )
+
+
+class ChrootUpdater:
+    """Updates a chroot."""
+
+    def __init__(self, build_id, series, arch):
+        self.build_id = build_id
+        self.series = series
+        self.arch = arch
+        self.chroot_path = os.path.join(
+            os.environ["HOME"], "build-" + build_id, "chroot-autobuild")
+
+    def chroot(self, args, env=None, **kwargs):
+        """Run a command in the chroot.
+
+        :param args: the command and arguments to run.
+        """
+        if env:
+            args = ["env"] + [
+                "%s=%s" % (key, shell_escape(value))
+                for key, value in env.items()] + args
+        args = set_personality(args, self.arch, series=self.series)
+        cmd = ["/usr/bin/sudo", "/usr/sbin/chroot", self.chroot_path] + args
+        subprocess.check_call(cmd, **kwargs)
+
+    def update(self):
+        with open("/dev/null", "r") as devnull:
+            env = {
+                "LANG": "C",
+                "DEBIAN_FRONTEND": "noninteractive",
+                "TTY": "unknown",
+                }
+            apt_get = "/usr/bin/apt-get"
+            update_args = [apt_get, "-uy", "update"]
+            try:
+                self.chroot(update_args, env=env, stdin=devnull)
+            except subprocess.CalledProcessError:
+                print(
+                    "Waiting 15 seconds and trying again ...", file=sys.stderr)
+                time.sleep(15)
+                self.chroot(update_args, env=env, stdin=devnull)
+            upgrade_args = [
+                apt_get, "-o", "DPkg::Options::=--force-confold", "-uy",
+                "--purge", "dist-upgrade",
+                ]
+            self.chroot(upgrade_args, env=env, stdin=devnull)

=== added directory 'lpbuildd/target/tests'
=== added file 'lpbuildd/target/tests/__init__.py'
=== added file 'lpbuildd/target/tests/test_chroot.py'
--- lpbuildd/target/tests/test_chroot.py	1970-01-01 00:00:00 +0000
+++ lpbuildd/target/tests/test_chroot.py	2017-07-28 14:04:50 +0000
@@ -0,0 +1,102 @@
+# Copyright 2017 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+import sys
+import time
+
+from fixtures import (
+    EnvironmentVariable,
+    MockPatch,
+    )
+from systemfixtures import (
+    FakeProcesses,
+    FakeTime,
+    )
+from testtools import TestCase
+
+from lpbuildd.target.chroot import ChrootUpdater
+
+
+class TestChrootUpdater(TestCase):
+
+    def test_chroot(self):
+        self.useFixture(EnvironmentVariable("HOME", "/expected/home"))
+        processes_fixture = self.useFixture(FakeProcesses())
+        processes_fixture.add(lambda _: {}, name="/usr/bin/sudo")
+        ChrootUpdater("1", "xenial", "amd64").chroot(
+            ["apt-get", "update"], env={"LANG": "C"})
+
+        expected_args = [
+            ["/usr/bin/sudo", "/usr/sbin/chroot",
+             "/expected/home/build-1/chroot-autobuild",
+             "linux64", "env", "LANG=C", "apt-get", "update"],
+            ]
+        self.assertEqual(
+            expected_args,
+            [proc._args["args"] for proc in processes_fixture.procs])
+
+    def test_update_succeeds(self):
+        self.useFixture(EnvironmentVariable("HOME", "/expected/home"))
+        processes_fixture = self.useFixture(FakeProcesses())
+        processes_fixture.add(lambda _: {}, name="/usr/bin/sudo")
+        self.useFixture(FakeTime())
+        start_time = time.time()
+        ChrootUpdater("1", "xenial", "amd64").update()
+
+        apt_get_args = [
+            "/usr/bin/sudo", "/usr/sbin/chroot",
+            "/expected/home/build-1/chroot-autobuild",
+            "linux64", "env",
+            "LANG=C",
+            "DEBIAN_FRONTEND=noninteractive",
+            "TTY=unknown",
+            "/usr/bin/apt-get",
+            ]
+        expected_args = [
+            apt_get_args + ["-uy", "update"],
+            apt_get_args + [
+                "-o", "DPkg::Options::=--force-confold", "-uy", "--purge",
+                "dist-upgrade",
+                ],
+            ]
+        self.assertEqual(
+            expected_args,
+            [proc._args["args"] for proc in processes_fixture.procs])
+        self.assertEqual(start_time, time.time())
+
+    def test_update_first_run_fails(self):
+        self.useFixture(EnvironmentVariable("HOME", "/expected/home"))
+        processes_fixture = self.useFixture(FakeProcesses())
+        apt_get_proc_infos = iter([{"returncode": 1}, {}, {}])
+        processes_fixture.add(
+            lambda _: next(apt_get_proc_infos), name="/usr/bin/sudo")
+        mock_print = self.useFixture(MockPatch("__builtin__.print")).mock
+        self.useFixture(FakeTime())
+        start_time = time.time()
+        ChrootUpdater("1", "xenial", "amd64").update()
+
+        apt_get_args = [
+            "/usr/bin/sudo", "/usr/sbin/chroot",
+            "/expected/home/build-1/chroot-autobuild",
+            "linux64", "env",
+            "LANG=C",
+            "DEBIAN_FRONTEND=noninteractive",
+            "TTY=unknown",
+            "/usr/bin/apt-get",
+            ]
+        expected_args = [
+            apt_get_args + ["-uy", "update"],
+            apt_get_args + ["-uy", "update"],
+            apt_get_args + [
+                "-o", "DPkg::Options::=--force-confold", "-uy", "--purge",
+                "dist-upgrade",
+                ],
+            ]
+        self.assertEqual(
+            expected_args,
+            [proc._args["args"] for proc in processes_fixture.procs])
+        mock_print.assert_called_once_with(
+            "Waiting 15 seconds and trying again ...", file=sys.stderr)
+        self.assertEqual(start_time + 15, time.time())

=== modified file 'lpbuildd/tests/test_binarypackage.py'
--- lpbuildd/tests/test_binarypackage.py	2016-12-02 14:20:28 +0000
+++ lpbuildd/tests/test_binarypackage.py	2017-07-28 14:04:50 +0000
@@ -1,4 +1,4 @@
-# Copyright 2013-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2013-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -95,7 +95,7 @@
         # after INIT.
         self.buildmanager.initiate(
             {'foo_1.dsc': dscname}, 'chroot.tar.gz',
-            {'distribution': 'ubuntu', 'suite': 'warty',
+            {'distribution': 'ubuntu', 'series': 'warty', 'suite': 'warty',
              'ogrecomponent': 'main'})
 
         os.makedirs(self.chrootdir)
@@ -170,7 +170,7 @@
         # appropriately, and does not pass DEB_BUILD_OPTIONS.
         self.buildmanager.initiate(
             {'foo_1.dsc': ''}, 'chroot.tar.gz',
-            {'distribution': 'ubuntu', 'suite': 'warty',
+            {'distribution': 'ubuntu', 'series': 'warty', 'suite': 'warty',
              'ogrecomponent': 'main', 'archive_purpose': 'PRIMARY',
              'build_debug_symbols': True})
         os.makedirs(self.chrootdir)
@@ -198,7 +198,7 @@
         # appropriately, and passes DEB_BUILD_OPTIONS=noautodbgsym.
         self.buildmanager.initiate(
             {'foo_1.dsc': ''}, 'chroot.tar.gz',
-            {'distribution': 'ubuntu', 'suite': 'warty',
+            {'distribution': 'ubuntu', 'series': 'warty', 'suite': 'warty',
              'ogrecomponent': 'main', 'archive_purpose': 'PRIMARY',
              'build_debug_symbols': False})
         os.makedirs(self.chrootdir)
@@ -281,7 +281,7 @@
         # pretends that it was terminated by a signal.
         self.buildmanager.initiate(
             {'foo_1.dsc': ''}, 'chroot.tar.gz',
-            {'distribution': 'ubuntu', 'suite': 'warty',
+            {'distribution': 'ubuntu', 'series': 'warty', 'suite': 'warty',
              'ogrecomponent': 'main'})
 
         self.buildmanager.abort()
@@ -493,7 +493,7 @@
         # returns those relevant to the current architecture.
         self.buildmanager.initiate(
             {'foo_1.dsc': ''}, 'chroot.tar.gz',
-            {'distribution': 'ubuntu', 'suite': 'warty',
+            {'distribution': 'ubuntu', 'series': 'warty', 'suite': 'warty',
              'ogrecomponent': 'main', 'arch_tag': 'i386'})
         self.assertEqual(
             "foo (>= 1)",
@@ -507,7 +507,7 @@
         # from the unsatisfied build-dependencies it returns.
         self.buildmanager.initiate(
             {'foo_1.dsc': ''}, 'chroot.tar.gz',
-            {'distribution': 'ubuntu', 'suite': 'warty',
+            {'distribution': 'ubuntu', 'series': 'warty', 'suite': 'warty',
              'ogrecomponent': 'main', 'arch_tag': 'i386'})
         self.assertEqual(
             "foo",
@@ -521,7 +521,7 @@
         # that evaluate to true when no build profiles are active.
         self.buildmanager.initiate(
             {'foo_1.dsc': ''}, 'chroot.tar.gz',
-            {'distribution': 'ubuntu', 'suite': 'warty',
+            {'distribution': 'ubuntu', 'series': 'warty', 'suite': 'warty',
              'ogrecomponent': 'main', 'arch_tag': 'i386'})
         self.assertEqual(
             "foo",

=== modified file 'lpbuildd/tests/test_debian.py'
--- lpbuildd/tests/test_debian.py	2017-04-26 12:24:32 +0000
+++ lpbuildd/tests/test_debian.py	2017-07-28 14:04:50 +0000
@@ -98,6 +98,7 @@
             'archives': [
                 'deb http://ppa.launchpad.dev/owner/name/ubuntu xenial main',
                 ],
+            'series': 'xenial',
             }
         self.startBuild(extra_args)
 
@@ -136,7 +137,8 @@
         self.assertEqual(DebianBuildState.UPDATE, self.getState())
         self.assertEqual(
             (['sharepath/slavebin/update-debian-chroot',
-              'update-debian-chroot', self.buildid, 'amd64'],
+              'update-debian-chroot', '--series', 'xenial', '--arch', 'amd64',
+              self.buildid],
              None),
             self.buildmanager.commands[-1])
         self.assertEqual(
@@ -194,6 +196,7 @@
             'archives': [
                 'deb http://ppa.launchpad.dev/owner/name/ubuntu xenial main',
                 ],
+            'series': 'xenial',
             'trusted_keys': [base64.b64encode(b'key material')],
             }
         self.startBuild(extra_args)
@@ -243,7 +246,8 @@
         self.assertEqual(DebianBuildState.UPDATE, self.getState())
         self.assertEqual(
             (['sharepath/slavebin/update-debian-chroot',
-              'update-debian-chroot', self.buildid, 'amd64'],
+              'update-debian-chroot', '--series', 'xenial', '--arch', 'amd64',
+              self.buildid],
              None),
             self.buildmanager.commands[-1])
         self.assertEqual(

=== modified file 'lpbuildd/tests/test_snap.py'
--- lpbuildd/tests/test_snap.py	2017-04-03 12:30:15 +0000
+++ lpbuildd/tests/test_snap.py	2017-07-28 14:04:50 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2015-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -56,6 +56,7 @@
         # The build manager's iterate() kicks off the consecutive states
         # after INIT.
         extra_args = {
+            "series": "xenial",
             "arch_tag": "i386",
             "name": "test-snap",
             "git_repository": "https://git.launchpad.dev/~example/+git/snap";,

=== modified file 'lpbuildd/tests/test_sourcepackagerecipe.py'
--- lpbuildd/tests/test_sourcepackagerecipe.py	2016-01-05 14:05:42 +0000
+++ lpbuildd/tests/test_sourcepackagerecipe.py	2017-07-28 14:04:50 +0000
@@ -1,4 +1,4 @@
-# Copyright 2013 Canonical Ltd.  This software is licensed under the
+# Copyright 2013-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -61,6 +61,7 @@
             'recipe_text': dedent("""\
                 # bzr-builder format 0.2 deb-version {debupstream}-0~{revno}
                 http://bazaar.launchpad.dev/~ppa-user/+junk/wakeonlan""";),
+            'series': 'maverick',
             'suite': 'maverick',
             'ogrecomponent': 'universe',
             'author_name': 'Steve\u1234',

=== modified file 'lpbuildd/tests/test_translationtemplatesbuildmanager.py'
--- lpbuildd/tests/test_translationtemplatesbuildmanager.py	2015-05-11 05:55:24 +0000
+++ lpbuildd/tests/test_translationtemplatesbuildmanager.py	2017-07-28 14:04:50 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010, 2011 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -56,7 +56,8 @@
         url = 'lp:~my/branch'
         # The build manager's iterate() kicks off the consecutive states
         # after INIT.
-        self.buildmanager.initiate({}, 'chroot.tar.gz', {'branch_url': url})
+        self.buildmanager.initiate(
+            {}, 'chroot.tar.gz', {'series': 'xenial', 'branch_url': url})
 
         # Skip states that are done in DebianBuildManager to the state
         # directly before INSTALL.
@@ -132,7 +133,8 @@
         url = 'lp:~my/branch'
         # The build manager's iterate() kicks off the consecutive states
         # after INIT.
-        self.buildmanager.initiate({}, 'chroot.tar.gz', {'branch_url': url})
+        self.buildmanager.initiate(
+            {}, 'chroot.tar.gz', {'series': 'xenial', 'branch_url': url})
 
         # Skip states to the INSTALL state.
         self.buildmanager._state = TranslationTemplatesBuildState.INSTALL
@@ -154,7 +156,8 @@
         url = 'lp:~my/branch'
         # The build manager's iterate() kicks off the consecutive states
         # after INIT.
-        self.buildmanager.initiate({}, 'chroot.tar.gz', {'branch_url': url})
+        self.buildmanager.initiate(
+            {}, 'chroot.tar.gz', {'series': 'xenial', 'branch_url': url})
 
         # Skip states to the INSTALL state.
         self.buildmanager._state = TranslationTemplatesBuildState.GENERATE

=== added file 'lpbuildd/tests/test_util.py'
--- lpbuildd/tests/test_util.py	1970-01-01 00:00:00 +0000
+++ lpbuildd/tests/test_util.py	2017-07-28 14:04:50 +0000
@@ -0,0 +1,59 @@
+# Copyright 2017 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+from testtools import TestCase
+
+from lpbuildd.util import (
+    get_arch_bits,
+    set_personality,
+    shell_escape,
+    )
+
+
+class TestShellEscape(TestCase):
+
+    def test_plain(self):
+        self.assertEqual("foo", shell_escape("foo"))
+
+    def test_whitespace(self):
+        self.assertEqual("'  '", shell_escape("  "))
+
+    def test_single_quotes(self):
+        self.assertEqual("'shell'\\''s great'", shell_escape("shell's great"))
+
+
+class TestGetArchBits(TestCase):
+
+    def test_x32(self):
+        self.assertEqual(64, get_arch_bits("x32"))
+
+    def test_32bit(self):
+        self.assertEqual(32, get_arch_bits("armhf"))
+        self.assertEqual(32, get_arch_bits("i386"))
+
+    def test_64bit(self):
+        self.assertEqual(64, get_arch_bits("amd64"))
+        self.assertEqual(64, get_arch_bits("arm64"))
+
+
+class TestSetPersonality(TestCase):
+
+    def test_32bit(self):
+        self.assertEqual(
+            ["linux32", "sbuild"], set_personality(["sbuild"], "i386"))
+
+    def test_64bit(self):
+        self.assertEqual(
+            ["linux64", "sbuild"], set_personality(["sbuild"], "amd64"))
+
+    def test_uname_26(self):
+        self.assertEqual(
+            ["linux64", "--uname-2.6", "sbuild"],
+            set_personality(["sbuild"], "amd64", series="precise"))
+
+    def test_no_uname_26(self):
+        self.assertEqual(
+            ["linux64", "sbuild"],
+            set_personality(["sbuild"], "amd64", series="trusty"))

=== modified file 'lpbuildd/util.py'
--- lpbuildd/util.py	2015-07-31 11:54:07 +0000
+++ lpbuildd/util.py	2017-07-28 14:04:50 +0000
@@ -1,9 +1,10 @@
-# Copyright 2015 Canonical Ltd.  This software is licensed under the
+# Copyright 2015-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
 
 import re
+import subprocess
 
 
 non_meta_re = re.compile(r'^[a-zA-Z0-9+,./:=@_-]+$')
@@ -15,36 +16,34 @@
         return "'%s'" % arg.replace("'", "'\\''")
 
 
-linux32_arches = [
-    "armel",
-    "armhf",
-    "hppa",
-    "i386",
-    "lpia",
-    "mips",
-    "mipsel",
-    "powerpc",
-    "s390",
-    "sparc",
-    ]
-linux64_arches = [
-    "alpha",
-    "amd64",
-    "arm64",
-    "hppa64",
-    "ia64",
-    "ppc64",
-    "ppc64el",
-    "s390x",
-    "sparc64",
-    "x32",
-    ]
-
-
-def set_personality(arch, args):
-    if arch in linux32_arches:
-        return ["linux32"] + args
-    elif arch in linux64_arches:
-        return ["linux64"] + args
-    else:
-        return args
+def get_arch_bits(arch):
+    if arch == "x32":
+        # x32 is an exception: the userspace is 32-bit, but it expects to be
+        # running on a 64-bit kernel.
+        return 64
+    else:
+        bits = subprocess.check_output(
+            ["dpkg-architecture", "-a%s" % arch,
+             "-qDEB_HOST_ARCH_BITS"]).rstrip("\n")
+        if bits == "32":
+            return 32
+        elif bits == "64":
+            return 64
+        else:
+            raise RuntimeError(
+                "Don't know how to deal with architecture %s "
+                "(DEB_HOST_ARCH_BITS=%s)" % (arch, bits))
+
+
+def set_personality(args, arch, series=None):
+    bits = get_arch_bits(arch)
+    assert bits in (32, 64)
+    if bits == 32:
+        setarch_cmd = ["linux32"]
+    else:
+        setarch_cmd = ["linux64"]
+
+    if series in ("hardy", "lucid", "maverick", "natty", "oneiric", "precise"):
+        setarch_cmd.append("--uname-2.6")
+
+    return setarch_cmd + args