← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~mwhudson/launchpad-buildd:variant-binarypackage-builds into launchpad-buildd:master

 

Michael Hudson-Doyle has proposed merging ~mwhudson/launchpad-buildd:variant-binarypackage-builds into launchpad-buildd:master with ~mwhudson/launchpad-buildd:separate-isa-abi-tags as a prerequisite.

Commit message:
Support architecture variants for binarypackage builds.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~mwhudson/launchpad-buildd/+git/launchpad-buildd/+merge/490635
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~mwhudson/launchpad-buildd:variant-binarypackage-builds into launchpad-buildd:master.
diff --git a/lpbuildd/binarypackage.py b/lpbuildd/binarypackage.py
index 5b7bc54..0495279 100644
--- a/lpbuildd/binarypackage.py
+++ b/lpbuildd/binarypackage.py
@@ -68,14 +68,18 @@ class DpkgArchitectureCache:
     def __init__(self):
         self._matches = {}
 
-    def match(self, arch, wildcard):
-        if (arch, wildcard) not in self._matches:
-            command = ["dpkg-architecture", "-a%s" % arch, "-i%s" % wildcard]
+    def match(self, abi_tag, wildcard):
+        if (abi_tag, wildcard) not in self._matches:
+            command = [
+                "dpkg-architecture",
+                "-a%s" % abi_tag,
+                "-i%s" % wildcard,
+            ]
             env = dict(os.environ)
             env.pop("DEB_HOST_ARCH", None)
             ret = subprocess.call(command, env=env) == 0
-            self._matches[(arch, wildcard)] = ret
-        return self._matches[(arch, wildcard)]
+            self._matches[(abi_tag, wildcard)] = ret
+        return self._matches[(abi_tag, wildcard)]
 
 
 dpkg_architecture = DpkgArchitectureCache()
@@ -178,10 +182,10 @@ class BinaryPackageBuildManager(DebianBuildManager):
             currently_building.write(currently_building_contents)
             os.fchmod(currently_building.fileno(), 0o644)
 
-        args = ["sbuild-package", self._buildid, self.arch_tag]
+        args = ["sbuild-package", self._buildid, self.abi_tag]
         args.append(self.suite)
         args.extend(["-c", "chroot:build-%s" % self._buildid])
-        args.append("--arch=" + self.arch_tag)
+        args.append("--arch=" + self.abi_tag)
         args.append("--dist=" + self.suite)
         args.append("--nolog")
         if self.arch_indep:
@@ -192,6 +196,10 @@ class BinaryPackageBuildManager(DebianBuildManager):
             env.pop("DEB_BUILD_OPTIONS", None)
         else:
             env["DEB_BUILD_OPTIONS"] = "noautodbgsym"
+        if self.isa_tag != self.abi_tag:
+            env["DEB_HOST_ARCH_VARIANT"] = self.isa_tag
+            suffix = self.isa_tag[len(self.abi_tag) :]
+            args.append("--dpkg-file-suffix=" + suffix)
         self.runSubProcess(self._sbuildpath, args, env=env)
 
     def getAptLists(self):
@@ -305,7 +313,7 @@ class BinaryPackageBuildManager(DebianBuildManager):
         if dep_arch is not None:
             arch_match = False
             for enabled, arch_wildcard in dep_arch:
-                if dpkg_architecture.match(self.arch_tag, arch_wildcard):
+                if dpkg_architecture.match(self.abi_tag, arch_wildcard):
                     arch_match = enabled
                     break
                 elif not enabled:
diff --git a/lpbuildd/builder.py b/lpbuildd/builder.py
index 7ffa3be..857d70c 100644
--- a/lpbuildd/builder.py
+++ b/lpbuildd/builder.py
@@ -193,8 +193,8 @@ class BuildManager:
             command,
             "--backend=%s" % self.backend_name,
             "--series=%s" % self.series,
-            "--abi-tag=%s" % self.arch_tag,
-            "--isa-tag=%s" % self.arch_tag,
+            "--abi-tag=%s" % self.abi_tag,
+            "--isa-tag=%s" % self.isa_tag,
         ]
         for constraint in self.constraints:
             base_args.append("--constraint=%s" % constraint)
@@ -279,7 +279,13 @@ class BuildManager:
 
         self.image_type = extra_args.get("image_type", "chroot")
         self.series = extra_args["series"]
-        self.arch_tag = extra_args.get("arch_tag", self._builder.getArch())
+        if "abi_tag" in extra_args and "isa_tag" in extra_args:
+            self.abi_tag = extra_args["abi_tag"]
+            self.isa_tag = extra_args["isa_tag"]
+        else:
+            self.abi_tag = self.isa_tag = extra_args.get(
+                "arch_tag", self._builder.getArch()
+            )
         self.fast_cleanup = extra_args.get("fast_cleanup", False)
         self.constraints = extra_args.get("builder_constraints") or []
 
@@ -294,7 +300,8 @@ class BuildManager:
             self.backend_name,
             self._buildid,
             series=self.series,
-            arch=self.arch_tag,
+            abi_tag=self.abi_tag,
+            isa_tag=self.isa_tag,
             constraints=self.constraints,
         )
 
diff --git a/lpbuildd/debian.py b/lpbuildd/debian.py
index 5e52884..5be4fa6 100644
--- a/lpbuildd/debian.py
+++ b/lpbuildd/debian.py
@@ -102,7 +102,7 @@ class DebianBuildManager(BuildManager):
                 yield filename
 
     def getChangesFilename(self):
-        changes = self._dscfile[:-4] + "_" + self.arch_tag + ".changes"
+        changes = self._dscfile[:-4] + "_" + self.isa_tag + ".changes"
         return get_build_path(self.home, self._buildid, changes)
 
     def gatherResults(self):
diff --git a/lpbuildd/target/backend.py b/lpbuildd/target/backend.py
index 371af8f..1d6222f 100644
--- a/lpbuildd/target/backend.py
+++ b/lpbuildd/target/backend.py
@@ -32,10 +32,18 @@ class Backend:
 
     supports_snapd = False
 
-    def __init__(self, build_id, series=None, arch=None, constraints=None):
+    def __init__(
+        self,
+        build_id,
+        series=None,
+        abi_tag=None,
+        isa_tag=None,
+        constraints=None,
+    ):
         self.build_id = build_id
         self.series = series
-        self.arch = arch
+        self.abi_tag = abi_tag
+        self.isa_tag = isa_tag
         self.constraints = constraints or []
         self.build_path = os.path.join(os.environ["HOME"], "build-" + build_id)
 
@@ -228,7 +236,9 @@ class Backend:
             rmtree(tmp_dir)
 
 
-def make_backend(name, build_id, series=None, arch=None, constraints=None):
+def make_backend(
+    name, build_id, series=None, abi_tag=None, isa_tag=None, constraints=None
+):
     if name == "chroot":
         from lpbuildd.target.chroot import Chroot
 
@@ -250,5 +260,9 @@ def make_backend(name, build_id, series=None, arch=None, constraints=None):
     else:
         raise KeyError("Unknown backend: %s" % name)
     return backend_factory(
-        build_id, series=series, arch=arch, constraints=constraints
+        build_id,
+        series=series,
+        abi_tag=abi_tag,
+        isa_tag=isa_tag,
+        constraints=constraints,
     )
diff --git a/lpbuildd/target/chroot.py b/lpbuildd/target/chroot.py
index d690ec1..0eca4f0 100644
--- a/lpbuildd/target/chroot.py
+++ b/lpbuildd/target/chroot.py
@@ -63,8 +63,8 @@ class Chroot(Backend):
                 + [f"{key}={value}" for key, value in env.items()]
                 + args
             )
-        if self.arch is not None:
-            args = set_personality(args, self.arch, series=self.series)
+        if self.abi_tag is not None:
+            args = set_personality(args, self.abi_tag, series=self.series)
         if cwd is not None:
             # This requires either a helper program in the chroot or
             # unpleasant quoting.  For now we go for the unpleasant quoting,
diff --git a/lpbuildd/target/lxd.py b/lpbuildd/target/lxd.py
index ab35590..dc3bafb 100644
--- a/lpbuildd/target/lxd.py
+++ b/lpbuildd/target/lxd.py
@@ -117,11 +117,11 @@ class LXD(Backend):
 
     @property
     def lxc_arch(self):
-        return self.arches[self.arch]
+        return self.arches[self.abi_tag]
 
     @property
     def alias(self):
-        return f"lp-{self.series}-{self.arch}"
+        return f"lp-{self.series}-{self.abi_tag}"
 
     @property
     def name(self):
@@ -142,9 +142,9 @@ class LXD(Backend):
             "properties": {
                 "os": "Ubuntu",
                 "series": self.series,
-                "architecture": self.arch,
+                "architecture": self.abi_tag,
                 "description": (
-                    f"Launchpad chroot for Ubuntu {self.series} ({self.arch})"
+                    f"Launchpad chroot for Ubuntu {self.series} ({self.abi_tag})"
                 ),
             },
         }
@@ -476,7 +476,7 @@ class LXD(Backend):
 
         # Linux 4.4 on powerpc doesn't support all the seccomp bits that LXD
         # needs.
-        if self.arch == "powerpc":
+        if self.abi_tag == "powerpc":
             raw_lxc_config.append(("lxc.seccomp", ""))
         config = {
             "security.privileged": "true",
@@ -682,7 +682,7 @@ class LXD(Backend):
             ]
         )
 
-        if self.arch == "armhf":
+        if self.abi_tag == "armhf":
             # Work around https://github.com/lxc/lxcfs/issues/553.  In
             # principle that could result in over-reporting the number of
             # available CPU cores, but that isn't a concern in
@@ -731,8 +731,8 @@ class LXD(Backend):
         if env:
             for key, value in env.items():
                 env_params.extend(["--env", f"{key}={value}"])
-        if self.arch is not None:
-            args = set_personality(args, self.arch, series=self.series)
+        if self.abi_tag is not None:
+            args = set_personality(args, self.abi_tag, series=self.series)
         if cwd is not None:
             # This requires either a helper program in the chroot or
             # unpleasant quoting.  For now we go for the unpleasant quoting,
diff --git a/lpbuildd/target/operation.py b/lpbuildd/target/operation.py
index 1fd248c..1c7b7f3 100644
--- a/lpbuildd/target/operation.py
+++ b/lpbuildd/target/operation.py
@@ -30,7 +30,7 @@ class Operation:
         parser.add_argument(
             "--isa-tag",
             metavar="ISA",
-            help="(currently ununsed)",
+            help="build for ISA",
         )
         parser.add_argument(
             "--constraint",
@@ -49,7 +49,8 @@ class Operation:
             self.args.backend,
             self.args.build_id,
             series=self.args.series,
-            arch=self.args.abi_tag,
+            abi_tag=self.args.abi_tag,
+            isa_tag=self.args.isa_tag,
             constraints=self.args.constraints,
         )
 
diff --git a/lpbuildd/tests/fakebuilder.py b/lpbuildd/tests/fakebuilder.py
index 15a820a..c3bafe1 100644
--- a/lpbuildd/tests/fakebuilder.py
+++ b/lpbuildd/tests/fakebuilder.py
@@ -259,8 +259,8 @@ class UncontainedBackend(Backend):
                 ]
                 + args
             )
-        if self.arch is not None:
-            args = set_personality(args, self.arch, series=self.series)
+        if self.abi_tag is not None:
+            args = set_personality(args, self.abi_tag, series=self.series)
         if input_text is None and not get_output:
             subprocess.check_call(args, cwd=cwd, **kwargs)
         else:
diff --git a/lpbuildd/tests/test_binarypackage.py b/lpbuildd/tests/test_binarypackage.py
index 3e007fc..51d007c 100644
--- a/lpbuildd/tests/test_binarypackage.py
+++ b/lpbuildd/tests/test_binarypackage.py
@@ -19,6 +19,7 @@ from testtools.matchers import (
     Is,
     MatchesListwise,
     Not,
+    StartsWith,
 )
 from testtools.twistedsupport import AsynchronousDeferredRunTest
 from twisted.internet import defer
@@ -112,13 +113,23 @@ class TestBinaryPackageBuildManagerIteration(TestCase):
         return self.buildmanager._state
 
     @defer.inlineCallbacks
-    def startBuild(self, dscname=""):
+    def startBuild(self, dscname="", abi_tag=None, isa_tag=None):
         # The build manager's iterate() kicks off the consecutive states
         # after INIT.
+        extra_args = {
+            "series": "warty",
+            "suite": "warty",
+            "ogrecomponent": "main",
+        }
+        if abi_tag is not None:
+            extra_args["abi_tag"] = abi_tag
+        if isa_tag is not None:
+            extra_args["isa_tag"] = isa_tag
+
         self.buildmanager.initiate(
             {"foo_1.dsc": dscname},
             "chroot.tar.gz",
-            {"series": "warty", "suite": "warty", "ogrecomponent": "main"},
+            extra_args,
         )
 
         os.makedirs(self.chrootdir)
@@ -127,30 +138,51 @@ class TestBinaryPackageBuildManagerIteration(TestCase):
         # SBUILD.
         self.buildmanager._state = BinaryPackageBuildState.UPDATE
 
+        sbuild_args = [
+            "sharepath/bin/sbuild-package",
+            "sbuild-package",
+            self.buildid,
+            "i386",
+            "warty",
+            "-c",
+            "chroot:build-" + self.buildid,
+            "--arch=i386",
+            "--dist=warty",
+            "--nolog",
+            "foo_1.dsc",
+        ]
+
+        if abi_tag != isa_tag:
+            env_matcher = ContainsDict(
+                {"DEB_HOST_ARCH_VARIANT": Equals(isa_tag)}
+            )
+            suffix = isa_tag[len(abi_tag) :]
+            sbuild_args.append("--dpkg-file-suffix=" + suffix)
+        else:
+            env_matcher = Not(Contains("DEB_HOST_ARCH_VARIANT"))
+
         # SBUILD: Build the package.
         yield self.buildmanager.iterate(0)
         self.assertState(
             BinaryPackageBuildState.SBUILD,
-            [
-                "sharepath/bin/sbuild-package",
-                "sbuild-package",
-                self.buildid,
-                "i386",
-                "warty",
-                "-c",
-                "chroot:build-" + self.buildid,
-                "--arch=i386",
-                "--dist=warty",
-                "--nolog",
-                "foo_1.dsc",
-            ],
+            sbuild_args,
+            env_matcher=env_matcher,
             final=True,
         )
         self.assertFalse(self.builder.wasCalled("chrootFail"))
 
     def assertState(self, state, command, env_matcher=None, final=False):
         self.assertEqual(state, self.getState())
-        self.assertEqual(command, self.buildmanager.commands[-1][0])
+        matchers = []
+        for arg in command:
+            if isinstance(arg, str):
+                matchers.append(Equals(arg))
+            else:
+                matchers.append(arg)
+        self.assertThat(
+            self.buildmanager.commands[-1][0],
+            MatchesListwise(matchers),
+        )
         if env_matcher is not None:
             self.assertThat(self.buildmanager.commands[-1][1], env_matcher)
         if final:
@@ -174,8 +206,8 @@ class TestBinaryPackageBuildManagerIteration(TestCase):
                 "scan-for-processes",
                 "--backend=chroot",
                 "--series=warty",
-                "--abi-tag=i386",
-                "--isa-tag=i386",
+                StartsWith("--abi-tag="),
+                StartsWith("--isa-tag="),
                 self.buildid,
             ],
             final=False,
@@ -191,8 +223,8 @@ class TestBinaryPackageBuildManagerIteration(TestCase):
                 "umount-chroot",
                 "--backend=chroot",
                 "--series=warty",
-                "--abi-tag=i386",
-                "--isa-tag=i386",
+                StartsWith("--abi-tag="),
+                StartsWith("--isa-tag="),
                 self.buildid,
             ],
             final=True,
@@ -231,6 +263,38 @@ class TestBinaryPackageBuildManagerIteration(TestCase):
         self.assertFalse(self.builder.wasCalled("buildFail"))
 
     @defer.inlineCallbacks
+    def test_variant(self):
+        # The build manager iterates a normal build from start to finish.
+        yield self.startBuild(abi_tag="i386", isa_tag="i386sse")
+
+        write_file(
+            os.path.join(self.buildmanager._cachepath, "buildlog"),
+            "I am a build log.",
+        )
+        changes_path = os.path.join(
+            self.buildmanager.home,
+            "build-%s" % self.buildid,
+            "foo_1_i386sse.changes",
+        )
+        write_file(changes_path, "I am a changes file.")
+
+        # After building the package, reap processes.
+        yield self.assertScansSanely(SBuildExitCodes.OK)
+        self.assertFalse(self.builder.wasCalled("buildFail"))
+        self.assertThat(
+            self.builder,
+            HasWaitingFiles.byEquality(
+                {
+                    "foo_1_i386sse.changes": b"I am a changes file.",
+                }
+            ),
+        )
+
+        # Control returns to the DebianBuildManager in the UMOUNT state.
+        self.assertUnmountsSanely()
+        self.assertFalse(self.builder.wasCalled("buildFail"))
+
+    @defer.inlineCallbacks
     def test_with_debug_symbols(self):
         # A build with debug symbols sets up /CurrentlyBuilding
         # appropriately, and does not pass DEB_BUILD_OPTIONS.
diff --git a/lpbuildd/util.py b/lpbuildd/util.py
index 9231206..bd949ad 100644
--- a/lpbuildd/util.py
+++ b/lpbuildd/util.py
@@ -22,8 +22,8 @@ def shell_escape(s):
         return quote(s)
 
 
-def get_arch_bits(arch):
-    if arch == "x32":
+def get_arch_bits(abi_tag):
+    if abi_tag == "x32":
         # x32 is an exception: the userspace is 32-bit, but it expects to be
         # running on a 64-bit kernel.
         return 64
@@ -31,7 +31,7 @@ def get_arch_bits(arch):
         env = dict(os.environ)
         env.pop("DEB_HOST_ARCH_BITS", None)
         bits = subprocess.check_output(
-            ["dpkg-architecture", "-a%s" % arch, "-qDEB_HOST_ARCH_BITS"],
+            ["dpkg-architecture", "-a%s" % abi_tag, "-qDEB_HOST_ARCH_BITS"],
             env=env,
             universal_newlines=True,
         ).rstrip("\n")
@@ -42,12 +42,12 @@ def get_arch_bits(arch):
         else:
             raise RuntimeError(
                 "Don't know how to deal with architecture %s "
-                "(DEB_HOST_ARCH_BITS=%s)" % (arch, bits)
+                "(DEB_HOST_ARCH_BITS=%s)" % (abi_tag, bits)
             )
 
 
-def set_personality(args, arch, series=None):
-    bits = get_arch_bits(arch)
+def set_personality(args, abi_tag, series=None):
+    bits = get_arch_bits(abi_tag)
     assert bits in (32, 64)
     if bits == 32:
         setarch_cmd = ["linux32"]
diff --git a/sbuildrc b/sbuildrc
index 008f034..7da1464 100644
--- a/sbuildrc
+++ b/sbuildrc
@@ -30,8 +30,10 @@ $build_environment = {
 
 # We want to expose almost nothing from the buildd environment.
 # DEB_BUILD_OPTIONS is set by sbuild-package.
+# DEB_HOST_ARCH_VARIANT is set by binarypackage.py.
 $environment_filter = [
     '^DEB_BUILD_OPTIONS$',
+    '^DEB_HOST_ARCH_VARIANT$',
     ];
 
 # We're just going to throw the chroot away anyway.