← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/snap-daily-builds-request-builds into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/snap-daily-builds-request-builds into lp:launchpad with lp:~cjwatson/launchpad/git-ref-remote-blob as a prerequisite.

Commit message:
Make automatic builds of snaps honour build-on architectures.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/snap-daily-builds-request-builds/+merge/348187
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/snap-daily-builds-request-builds into lp:launchpad.
=== modified file 'lib/lp/snappy/adapters/buildarch.py'
--- lib/lp/snappy/adapters/buildarch.py	2018-06-18 22:36:14 +0000
+++ lib/lp/snappy/adapters/buildarch.py	2018-06-18 22:36:14 +0000
@@ -122,12 +122,12 @@
 
 
 def determine_architectures_to_build(snapcraft_data, supported_arches):
-    """Return a set of architectures to build based on snapcraft.yaml.
+    """Return a list of architectures to build based on snapcraft.yaml.
 
     :param snapcraft_data: A parsed snapcraft.yaml.
     :param supported_arches: An ordered list of all architecture tags that
         we can create builds for.
-    :return: a set of `SnapBuildInstance`s.
+    :return: a list of `SnapBuildInstance`s.
     """
     architectures_list = snapcraft_data.get("architectures")
 
@@ -162,10 +162,10 @@
     if duplicates:
         raise DuplicateBuildOnError(duplicates)
 
-    architectures_to_build = set()
+    architectures_to_build = []
     for arch in architectures:
         try:
-            architectures_to_build.add(
+            architectures_to_build.append(
                 SnapBuildInstance(arch, supported_arches))
         except UnsupportedBuildOnError:
             # Snaps are allowed to declare that they build on architectures

=== modified file 'lib/lp/snappy/interfaces/snap.py'
--- lib/lp/snappy/interfaces/snap.py	2018-06-18 22:36:14 +0000
+++ lib/lp/snappy/interfaces/snap.py	2018-06-18 22:36:14 +0000
@@ -406,6 +406,7 @@
         """
 
     def requestBuildsFromJob(requester, archive, pocket, channels=None,
+                             allow_failures=False, fetch_snapcraft_yaml=True,
                              logger=None):
         """Synchronous part of `Snap.requestBuilds`.
 
@@ -416,6 +417,13 @@
         :param pocket: The pocket that should be targeted.
         :param channels: A dictionary mapping snap names to channels to use
             for these builds.
+        :param allow_failures: If True, log exceptions other than "already
+            pending" from individual build requests; if False, raise them to
+            the caller.
+        :param fetch_snapcraft_yaml: If True, fetch snapcraft.yaml from the
+            appropriate branch or repository and use it to decide which
+            builds to request; if False, fall back to building for all
+            supported architectures.
         :param logger: An optional logger.
         :return: A sequence of `ISnapBuild` instances.
         """
@@ -474,12 +482,17 @@
     @operation_returns_collection_of(Interface)
     @export_write_operation()
     @operation_for_version("devel")
-    def requestAutoBuilds(allow_failures=False, logger=None):
+    def requestAutoBuilds(allow_failures=False, fetch_snapcraft_yaml=False,
+                          logger=None):
         """Create and return automatic builds for this snap package.
 
         :param allow_failures: If True, log exceptions other than "already
             pending" from individual build requests; if False, raise them to
             the caller.
+        :param fetch_snapcraft_yaml: If True, fetch snapcraft.yaml from the
+            appropriate branch or repository and use it to decide which
+            builds to request; if False, fall back to building for all
+            supported architectures.
         :param logger: An optional logger.
         :raises CannotRequestAutoBuilds: if no auto_build_archive or
             auto_build_pocket is set.

=== modified file 'lib/lp/snappy/model/snap.py'
--- lib/lp/snappy/model/snap.py	2018-06-18 22:36:14 +0000
+++ lib/lp/snappy/model/snap.py	2018-06-18 22:36:14 +0000
@@ -552,18 +552,22 @@
         return self.getBuildRequest(job.job_id)
 
     def requestBuildsFromJob(self, requester, archive, pocket, channels=None,
+                             allow_failures=False, fetch_snapcraft_yaml=True,
                              logger=None):
         """See `ISnap`."""
-        try:
-            snapcraft_data = removeSecurityProxy(
-                getUtility(ISnapSet).getSnapcraftYaml(self))
-        except CannotFetchSnapcraftYaml as e:
-            if not e.unsupported_remote:
-                raise
-            # The only reason we can't fetch the file is because we don't
-            # support fetching from this repository's host.  In this case
-            # the best thing is to fall back to building for all supported
-            # architectures.
+        if fetch_snapcraft_yaml:
+            try:
+                snapcraft_data = removeSecurityProxy(
+                    getUtility(ISnapSet).getSnapcraftYaml(self))
+            except CannotFetchSnapcraftYaml as e:
+                if not e.unsupported_remote:
+                    raise
+                # The only reason we can't fetch the file is because we
+                # don't support fetching from this repository's host.  In
+                # this case the best thing is to fall back to building for
+                # all supported architectures.
+                snapcraft_data = {}
+        else:
             snapcraft_data = {}
         # Sort by Processor.id for determinism.  This is chosen to be the
         # same order as in BinaryPackageBuildSet.createForSource, to
@@ -592,11 +596,18 @@
                     logger.warning(
                         " - %s/%s/%s: %s",
                         self.owner.name, self.name, arch, e)
+            except Exception as e:
+                if not allow_failures:
+                    raise
+                elif logger is not None:
+                    logger.exception(
+                        " - %s/%s/%s: %s",
+                        self.owner.name, self.name, arch, e)
         return builds
 
-    def requestAutoBuilds(self, allow_failures=False, logger=None):
+    def requestAutoBuilds(self, allow_failures=False,
+                          fetch_snapcraft_yaml=False, logger=None):
         """See `ISnap`."""
-        builds = []
         if self.auto_build_archive is None:
             raise CannotRequestAutoBuilds("auto_build_archive")
         if self.auto_build_pocket is None:
@@ -606,29 +617,10 @@
             logger.debug(
                 "Scheduling builds of snap package %s/%s",
                 self.owner.name, self.name)
-        for arch in self.getAllowedArchitectures():
-            try:
-                build = self.requestBuild(
-                    self.owner, self.auto_build_archive, arch,
-                    self.auto_build_pocket, self.auto_build_channels)
-                if logger is not None:
-                    logger.debug(
-                        " - %s/%s/%s: Build requested.",
-                        self.owner.name, self.name, arch.architecturetag)
-                builds.append(build)
-            except SnapBuildAlreadyPending as e:
-                if logger is not None:
-                    logger.warning(
-                        " - %s/%s/%s: %s",
-                        self.owner.name, self.name, arch.architecturetag, e)
-            except Exception as e:
-                if not allow_failures:
-                    raise
-                elif logger is not None:
-                    logger.exception(
-                        " - %s/%s/%s: %s",
-                        self.owner.name, self.name, arch.architecturetag, e)
-        return builds
+        return self.requestBuildsFromJob(
+            self.owner, self.auto_build_archive, self.auto_build_pocket,
+            channels=self.auto_build_channels, allow_failures=allow_failures,
+            fetch_snapcraft_yaml=fetch_snapcraft_yaml, logger=logger)
 
     def getBuildRequest(self, job_id):
         """See `ISnap`."""
@@ -1116,7 +1108,7 @@
         builds = []
         for snap in snaps:
             builds.extend(snap.requestAutoBuilds(
-                allow_failures=True, logger=logger))
+                allow_failures=True, fetch_snapcraft_yaml=True, logger=logger))
         return builds
 
     def detachFromBranch(self, branch):

=== modified file 'lib/lp/snappy/tests/test_snap.py'
--- lib/lp/snappy/tests/test_snap.py	2018-06-18 22:36:14 +0000
+++ lib/lp/snappy/tests/test_snap.py	2018-06-18 22:36:14 +0000
@@ -1285,9 +1285,10 @@
     def makeAutoBuildableSnap(self, **kwargs):
         processor = self.factory.makeProcessor(supports_virtualized=True)
         das = self.makeBuildableDistroArchSeries(processor=processor)
+        [git_ref] = self.factory.makeGitRefs()
         snap = self.factory.makeSnap(
             distroseries=das.distroseries, processors=[das.processor],
-            auto_build=True, **kwargs)
+            git_ref=git_ref, auto_build=True, **kwargs)
         return das, snap
 
     def test_makeAutoBuilds(self):
@@ -1296,7 +1297,11 @@
         self.assertEqual([], getUtility(ISnapSet).makeAutoBuilds())
         das, snap = self.makeAutoBuildableSnap(is_stale=True)
         logger = BufferLogger()
-        [build] = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
+        with GitHostingFixture(blob=dedent("""\
+                architectures:
+                  - build-on: %s
+                """) % das.architecturetag):
+            [build] = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
         self.assertThat(build, MatchesStructure.byEquality(
             requester=snap.owner, snap=snap, distro_arch_series=das,
             status=BuildStatus.NEEDSBUILD,
@@ -1319,7 +1324,11 @@
             requester=snap.owner, snap=snap, archive=snap.auto_build_archive,
             distroarchseries=das)
         logger = BufferLogger()
-        builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
+        with GitHostingFixture(blob=dedent("""\
+                architectures:
+                  - build-on: %s
+                """) % das.architecturetag):
+            builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
         self.assertEqual([], builds)
         self.assertEqual([], logger.getLogBuffer().splitlines())
 
@@ -1340,7 +1349,13 @@
             channels={"snapcraft": "stable"})
 
         logger = BufferLogger()
-        builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
+        snapcraft_yaml = dedent("""\
+            architectures:
+              - build-on: %s
+              - build-on: %s
+            """) % (das1.architecturetag, das2.architecturetag)
+        with GitHostingFixture(blob=snapcraft_yaml):
+            builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
         self.assertThat(builds, MatchesSetwise(
             MatchesStructure(
                 requester=Equals(snap1.owner), snap=Equals(snap1),
@@ -1369,14 +1384,19 @@
             removeSecurityProxy(snap).is_stale = True
             IStore(snap).flush()
         logger = BufferLogger()
-        builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
+        with GitHostingFixture(blob=snapcraft_yaml):
+            builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
         self.assertEqual([], builds)
         self.assertEqual([], logger.getLogBuffer().splitlines())
 
     def test_makeAutoBuilds_skips_non_stale_snaps(self):
         # ISnapSet.makeAutoBuilds skips snap packages that are not stale.
         das, snap = self.makeAutoBuildableSnap(is_stale=False)
-        self.assertEqual([], getUtility(ISnapSet).makeAutoBuilds())
+        with GitHostingFixture(blob=dedent("""\
+                architectures:
+                  - build-on: %s
+                """) % das.architecturetag):
+            self.assertEqual([], getUtility(ISnapSet).makeAutoBuilds())
 
     def test_makeAutoBuilds_skips_pending(self):
         # ISnapSet.makeAutoBuilds skips snap packages that already have
@@ -1389,7 +1409,11 @@
             distroarchseries=das,
             date_created=datetime.now(pytz.UTC) - timedelta(days=1))
         logger = BufferLogger()
-        builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
+        with GitHostingFixture(blob=dedent("""\
+                architectures:
+                  - build-on: %s
+                """) % das.architecturetag):
+            builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
         self.assertEqual([], builds)
         expected_log_entries = [
             "DEBUG Scheduling builds of snap package %s/%s" % (
@@ -1410,7 +1434,11 @@
             distroarchseries=das,
             date_created=datetime.now(pytz.UTC) - timedelta(days=1),
             status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=1))
-        builds = getUtility(ISnapSet).makeAutoBuilds()
+        with GitHostingFixture(blob=dedent("""\
+                architectures:
+                  - build-on: %s
+                """) % das.architecturetag):
+            builds = getUtility(ISnapSet).makeAutoBuilds()
         self.assertEqual(1, len(builds))
 
     def test_makeAutoBuilds_with_older_and_newer_builds(self):
@@ -1423,7 +1451,11 @@
                 archive=snap.auto_build_archive, distroarchseries=das,
                 date_created=datetime.now(pytz.UTC) - timediff,
                 status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=1))
-        self.assertEqual([], getUtility(ISnapSet).makeAutoBuilds())
+        with GitHostingFixture(blob=dedent("""\
+                architectures:
+                  - build-on: %s
+                """) % das.architecturetag):
+            self.assertEqual([], getUtility(ISnapSet).makeAutoBuilds())
 
     def test_makeAutoBuilds_with_recent_build_from_different_archive(self):
         # If a snap package has been built recently but from an archive
@@ -1434,9 +1466,49 @@
             requester=snap.owner, snap=snap, distroarchseries=das,
             date_created=datetime.now(pytz.UTC) - timedelta(minutes=30),
             status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=1))
-        builds = getUtility(ISnapSet).makeAutoBuilds()
+        with GitHostingFixture(blob=dedent("""\
+                architectures:
+                  - build-on: %s
+                """) % das.architecturetag):
+            builds = getUtility(ISnapSet).makeAutoBuilds()
         self.assertEqual(1, len(builds))
 
+    def test_makeAutoBuilds_honours_explicit_architectures(self):
+        # ISnapSet.makeAutoBuilds honours an explicit list of architectures
+        # in the snap's snapcraft.yaml.
+        das1, snap = self.makeAutoBuildableSnap(is_stale=True)
+        das2 = self.makeBuildableDistroArchSeries(
+            distroseries=das1.distroseries,
+            processor=self.factory.makeProcessor(supports_virtualized=True))
+        das3 = self.makeBuildableDistroArchSeries(
+            distroseries=das1.distroseries,
+            processor=self.factory.makeProcessor(supports_virtualized=True))
+        snap.setProcessors([das1.processor, das2.processor, das3.processor])
+        logger = BufferLogger()
+        with GitHostingFixture(blob=dedent("""\
+                architectures:
+                  - build-on: %s
+                  - build-on: %s
+                """) % (das1.architecturetag, das2.architecturetag)):
+            builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
+        self.assertThat(set(builds), MatchesSetwise(*[
+            MatchesStructure.byEquality(
+                requester=snap.owner, snap=snap, distro_arch_series=das,
+                status=BuildStatus.NEEDSBUILD)
+            for das in (das1, das2)
+            ]))
+        expected_log_entries = [
+            "DEBUG Scheduling builds of snap package %s/%s" % (
+                snap.owner.name, snap.name),
+            "DEBUG  - %s/%s/%s: Build requested." % (
+                snap.owner.name, snap.name, das1.architecturetag),
+            "DEBUG  - %s/%s/%s: Build requested." % (
+                snap.owner.name, snap.name, das2.architecturetag),
+            ]
+        self.assertEqual(
+            expected_log_entries, logger.getLogBuffer().splitlines())
+        self.assertFalse(snap.is_stale)
+
     def test_detachFromBranch(self):
         # ISnapSet.detachFromBranch clears the given Bazaar branch from all
         # Snaps.


Follow ups