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

Proposed by Colin Watson
Status: Merged
Merged at revision: 18740
Proposed branch: lp:~cjwatson/launchpad/snap-daily-builds-request-builds
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/git-ref-remote-blob
Diff against target: 353 lines (+128/-54)
4 files modified
lib/lp/snappy/adapters/buildarch.py (+4/-4)
lib/lp/snappy/interfaces/snap.py (+14/-1)
lib/lp/snappy/model/snap.py (+28/-36)
lib/lp/snappy/tests/test_snap.py (+82/-13)
To merge this branch: bzr merge lp:~cjwatson/launchpad/snap-daily-builds-request-builds
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+348187@code.launchpad.net

Commit message

Make automatic builds of snaps honour build-on architectures.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/snappy/adapters/buildarch.py'
--- lib/lp/snappy/adapters/buildarch.py 2018-06-14 17:11:56 +0000
+++ lib/lp/snappy/adapters/buildarch.py 2018-07-30 09:08:46 +0000
@@ -122,12 +122,12 @@
122122
123123
124def determine_architectures_to_build(snapcraft_data, supported_arches):124def determine_architectures_to_build(snapcraft_data, supported_arches):
125 """Return a set of architectures to build based on snapcraft.yaml.125 """Return a list of architectures to build based on snapcraft.yaml.
126126
127 :param snapcraft_data: A parsed snapcraft.yaml.127 :param snapcraft_data: A parsed snapcraft.yaml.
128 :param supported_arches: An ordered list of all architecture tags that128 :param supported_arches: An ordered list of all architecture tags that
129 we can create builds for.129 we can create builds for.
130 :return: a set of `SnapBuildInstance`s.130 :return: a list of `SnapBuildInstance`s.
131 """131 """
132 architectures_list = snapcraft_data.get("architectures")132 architectures_list = snapcraft_data.get("architectures")
133133
@@ -162,10 +162,10 @@
162 if duplicates:162 if duplicates:
163 raise DuplicateBuildOnError(duplicates)163 raise DuplicateBuildOnError(duplicates)
164164
165 architectures_to_build = set()165 architectures_to_build = []
166 for arch in architectures:166 for arch in architectures:
167 try:167 try:
168 architectures_to_build.add(168 architectures_to_build.append(
169 SnapBuildInstance(arch, supported_arches))169 SnapBuildInstance(arch, supported_arches))
170 except UnsupportedBuildOnError:170 except UnsupportedBuildOnError:
171 # Snaps are allowed to declare that they build on architectures171 # Snaps are allowed to declare that they build on architectures
172172
=== modified file 'lib/lp/snappy/interfaces/snap.py'
--- lib/lp/snappy/interfaces/snap.py 2018-06-18 16:42:43 +0000
+++ lib/lp/snappy/interfaces/snap.py 2018-07-30 09:08:46 +0000
@@ -406,6 +406,7 @@
406 """406 """
407407
408 def requestBuildsFromJob(requester, archive, pocket, channels=None,408 def requestBuildsFromJob(requester, archive, pocket, channels=None,
409 allow_failures=False, fetch_snapcraft_yaml=True,
409 logger=None):410 logger=None):
410 """Synchronous part of `Snap.requestBuilds`.411 """Synchronous part of `Snap.requestBuilds`.
411412
@@ -416,6 +417,13 @@
416 :param pocket: The pocket that should be targeted.417 :param pocket: The pocket that should be targeted.
417 :param channels: A dictionary mapping snap names to channels to use418 :param channels: A dictionary mapping snap names to channels to use
418 for these builds.419 for these builds.
420 :param allow_failures: If True, log exceptions other than "already
421 pending" from individual build requests; if False, raise them to
422 the caller.
423 :param fetch_snapcraft_yaml: If True, fetch snapcraft.yaml from the
424 appropriate branch or repository and use it to decide which
425 builds to request; if False, fall back to building for all
426 supported architectures.
419 :param logger: An optional logger.427 :param logger: An optional logger.
420 :return: A sequence of `ISnapBuild` instances.428 :return: A sequence of `ISnapBuild` instances.
421 """429 """
@@ -474,12 +482,17 @@
474 @operation_returns_collection_of(Interface)482 @operation_returns_collection_of(Interface)
475 @export_write_operation()483 @export_write_operation()
476 @operation_for_version("devel")484 @operation_for_version("devel")
477 def requestAutoBuilds(allow_failures=False, logger=None):485 def requestAutoBuilds(allow_failures=False, fetch_snapcraft_yaml=False,
486 logger=None):
478 """Create and return automatic builds for this snap package.487 """Create and return automatic builds for this snap package.
479488
480 :param allow_failures: If True, log exceptions other than "already489 :param allow_failures: If True, log exceptions other than "already
481 pending" from individual build requests; if False, raise them to490 pending" from individual build requests; if False, raise them to
482 the caller.491 the caller.
492 :param fetch_snapcraft_yaml: If True, fetch snapcraft.yaml from the
493 appropriate branch or repository and use it to decide which
494 builds to request; if False, fall back to building for all
495 supported architectures.
483 :param logger: An optional logger.496 :param logger: An optional logger.
484 :raises CannotRequestAutoBuilds: if no auto_build_archive or497 :raises CannotRequestAutoBuilds: if no auto_build_archive or
485 auto_build_pocket is set.498 auto_build_pocket is set.
486499
=== modified file 'lib/lp/snappy/model/snap.py'
--- lib/lp/snappy/model/snap.py 2018-07-13 09:17:40 +0000
+++ lib/lp/snappy/model/snap.py 2018-07-30 09:08:46 +0000
@@ -552,18 +552,22 @@
552 return self.getBuildRequest(job.job_id)552 return self.getBuildRequest(job.job_id)
553553
554 def requestBuildsFromJob(self, requester, archive, pocket, channels=None,554 def requestBuildsFromJob(self, requester, archive, pocket, channels=None,
555 allow_failures=False, fetch_snapcraft_yaml=True,
555 logger=None):556 logger=None):
556 """See `ISnap`."""557 """See `ISnap`."""
557 try:558 if fetch_snapcraft_yaml:
558 snapcraft_data = removeSecurityProxy(559 try:
559 getUtility(ISnapSet).getSnapcraftYaml(self))560 snapcraft_data = removeSecurityProxy(
560 except CannotFetchSnapcraftYaml as e:561 getUtility(ISnapSet).getSnapcraftYaml(self))
561 if not e.unsupported_remote:562 except CannotFetchSnapcraftYaml as e:
562 raise563 if not e.unsupported_remote:
563 # The only reason we can't fetch the file is because we don't564 raise
564 # support fetching from this repository's host. In this case565 # The only reason we can't fetch the file is because we
565 # the best thing is to fall back to building for all supported566 # don't support fetching from this repository's host. In
566 # architectures.567 # this case the best thing is to fall back to building for
568 # all supported architectures.
569 snapcraft_data = {}
570 else:
567 snapcraft_data = {}571 snapcraft_data = {}
568 # Sort by Processor.id for determinism. This is chosen to be the572 # Sort by Processor.id for determinism. This is chosen to be the
569 # same order as in BinaryPackageBuildSet.createForSource, to573 # same order as in BinaryPackageBuildSet.createForSource, to
@@ -589,11 +593,18 @@
589 builds.append(build)593 builds.append(build)
590 except SnapBuildAlreadyPending as e:594 except SnapBuildAlreadyPending as e:
591 pass595 pass
596 except Exception as e:
597 if not allow_failures:
598 raise
599 elif logger is not None:
600 logger.exception(
601 " - %s/%s/%s: %s",
602 self.owner.name, self.name, arch, e)
592 return builds603 return builds
593604
594 def requestAutoBuilds(self, allow_failures=False, logger=None):605 def requestAutoBuilds(self, allow_failures=False,
606 fetch_snapcraft_yaml=False, logger=None):
595 """See `ISnap`."""607 """See `ISnap`."""
596 builds = []
597 if self.auto_build_archive is None:608 if self.auto_build_archive is None:
598 raise CannotRequestAutoBuilds("auto_build_archive")609 raise CannotRequestAutoBuilds("auto_build_archive")
599 if self.auto_build_pocket is None:610 if self.auto_build_pocket is None:
@@ -603,29 +614,10 @@
603 logger.debug(614 logger.debug(
604 "Scheduling builds of snap package %s/%s",615 "Scheduling builds of snap package %s/%s",
605 self.owner.name, self.name)616 self.owner.name, self.name)
606 for arch in self.getAllowedArchitectures():617 return self.requestBuildsFromJob(
607 try:618 self.owner, self.auto_build_archive, self.auto_build_pocket,
608 build = self.requestBuild(619 channels=self.auto_build_channels, allow_failures=allow_failures,
609 self.owner, self.auto_build_archive, arch,620 fetch_snapcraft_yaml=fetch_snapcraft_yaml, logger=logger)
610 self.auto_build_pocket, self.auto_build_channels)
611 if logger is not None:
612 logger.debug(
613 " - %s/%s/%s: Build requested.",
614 self.owner.name, self.name, arch.architecturetag)
615 builds.append(build)
616 except SnapBuildAlreadyPending as e:
617 if logger is not None:
618 logger.warning(
619 " - %s/%s/%s: %s",
620 self.owner.name, self.name, arch.architecturetag, e)
621 except Exception as e:
622 if not allow_failures:
623 raise
624 elif logger is not None:
625 logger.exception(
626 " - %s/%s/%s: %s",
627 self.owner.name, self.name, arch.architecturetag, e)
628 return builds
629621
630 def getBuildRequest(self, job_id):622 def getBuildRequest(self, job_id):
631 """See `ISnap`."""623 """See `ISnap`."""
@@ -1113,7 +1105,7 @@
1113 builds = []1105 builds = []
1114 for snap in snaps:1106 for snap in snaps:
1115 builds.extend(snap.requestAutoBuilds(1107 builds.extend(snap.requestAutoBuilds(
1116 allow_failures=True, logger=logger))1108 allow_failures=True, fetch_snapcraft_yaml=True, logger=logger))
1117 return builds1109 return builds
11181110
1119 def detachFromBranch(self, branch):1111 def detachFromBranch(self, branch):
11201112
=== modified file 'lib/lp/snappy/tests/test_snap.py'
--- lib/lp/snappy/tests/test_snap.py 2018-06-18 16:42:43 +0000
+++ lib/lp/snappy/tests/test_snap.py 2018-07-30 09:08:46 +0000
@@ -1285,9 +1285,10 @@
1285 def makeAutoBuildableSnap(self, **kwargs):1285 def makeAutoBuildableSnap(self, **kwargs):
1286 processor = self.factory.makeProcessor(supports_virtualized=True)1286 processor = self.factory.makeProcessor(supports_virtualized=True)
1287 das = self.makeBuildableDistroArchSeries(processor=processor)1287 das = self.makeBuildableDistroArchSeries(processor=processor)
1288 [git_ref] = self.factory.makeGitRefs()
1288 snap = self.factory.makeSnap(1289 snap = self.factory.makeSnap(
1289 distroseries=das.distroseries, processors=[das.processor],1290 distroseries=das.distroseries, processors=[das.processor],
1290 auto_build=True, **kwargs)1291 git_ref=git_ref, auto_build=True, **kwargs)
1291 return das, snap1292 return das, snap
12921293
1293 def test_makeAutoBuilds(self):1294 def test_makeAutoBuilds(self):
@@ -1296,7 +1297,11 @@
1296 self.assertEqual([], getUtility(ISnapSet).makeAutoBuilds())1297 self.assertEqual([], getUtility(ISnapSet).makeAutoBuilds())
1297 das, snap = self.makeAutoBuildableSnap(is_stale=True)1298 das, snap = self.makeAutoBuildableSnap(is_stale=True)
1298 logger = BufferLogger()1299 logger = BufferLogger()
1299 [build] = getUtility(ISnapSet).makeAutoBuilds(logger=logger)1300 with GitHostingFixture(blob=dedent("""\
1301 architectures:
1302 - build-on: %s
1303 """) % das.architecturetag):
1304 [build] = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
1300 self.assertThat(build, MatchesStructure.byEquality(1305 self.assertThat(build, MatchesStructure.byEquality(
1301 requester=snap.owner, snap=snap, distro_arch_series=das,1306 requester=snap.owner, snap=snap, distro_arch_series=das,
1302 status=BuildStatus.NEEDSBUILD,1307 status=BuildStatus.NEEDSBUILD,
@@ -1319,7 +1324,11 @@
1319 requester=snap.owner, snap=snap, archive=snap.auto_build_archive,1324 requester=snap.owner, snap=snap, archive=snap.auto_build_archive,
1320 distroarchseries=das)1325 distroarchseries=das)
1321 logger = BufferLogger()1326 logger = BufferLogger()
1322 builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)1327 with GitHostingFixture(blob=dedent("""\
1328 architectures:
1329 - build-on: %s
1330 """) % das.architecturetag):
1331 builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
1323 self.assertEqual([], builds)1332 self.assertEqual([], builds)
1324 self.assertEqual([], logger.getLogBuffer().splitlines())1333 self.assertEqual([], logger.getLogBuffer().splitlines())
13251334
@@ -1340,7 +1349,13 @@
1340 channels={"snapcraft": "stable"})1349 channels={"snapcraft": "stable"})
13411350
1342 logger = BufferLogger()1351 logger = BufferLogger()
1343 builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)1352 snapcraft_yaml = dedent("""\
1353 architectures:
1354 - build-on: %s
1355 - build-on: %s
1356 """) % (das1.architecturetag, das2.architecturetag)
1357 with GitHostingFixture(blob=snapcraft_yaml):
1358 builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
1344 self.assertThat(builds, MatchesSetwise(1359 self.assertThat(builds, MatchesSetwise(
1345 MatchesStructure(1360 MatchesStructure(
1346 requester=Equals(snap1.owner), snap=Equals(snap1),1361 requester=Equals(snap1.owner), snap=Equals(snap1),
@@ -1369,14 +1384,19 @@
1369 removeSecurityProxy(snap).is_stale = True1384 removeSecurityProxy(snap).is_stale = True
1370 IStore(snap).flush()1385 IStore(snap).flush()
1371 logger = BufferLogger()1386 logger = BufferLogger()
1372 builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)1387 with GitHostingFixture(blob=snapcraft_yaml):
1388 builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
1373 self.assertEqual([], builds)1389 self.assertEqual([], builds)
1374 self.assertEqual([], logger.getLogBuffer().splitlines())1390 self.assertEqual([], logger.getLogBuffer().splitlines())
13751391
1376 def test_makeAutoBuilds_skips_non_stale_snaps(self):1392 def test_makeAutoBuilds_skips_non_stale_snaps(self):
1377 # ISnapSet.makeAutoBuilds skips snap packages that are not stale.1393 # ISnapSet.makeAutoBuilds skips snap packages that are not stale.
1378 das, snap = self.makeAutoBuildableSnap(is_stale=False)1394 das, snap = self.makeAutoBuildableSnap(is_stale=False)
1379 self.assertEqual([], getUtility(ISnapSet).makeAutoBuilds())1395 with GitHostingFixture(blob=dedent("""\
1396 architectures:
1397 - build-on: %s
1398 """) % das.architecturetag):
1399 self.assertEqual([], getUtility(ISnapSet).makeAutoBuilds())
13801400
1381 def test_makeAutoBuilds_skips_pending(self):1401 def test_makeAutoBuilds_skips_pending(self):
1382 # ISnapSet.makeAutoBuilds skips snap packages that already have1402 # ISnapSet.makeAutoBuilds skips snap packages that already have
@@ -1389,14 +1409,15 @@
1389 distroarchseries=das,1409 distroarchseries=das,
1390 date_created=datetime.now(pytz.UTC) - timedelta(days=1))1410 date_created=datetime.now(pytz.UTC) - timedelta(days=1))
1391 logger = BufferLogger()1411 logger = BufferLogger()
1392 builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)1412 with GitHostingFixture(blob=dedent("""\
1413 architectures:
1414 - build-on: %s
1415 """) % das.architecturetag):
1416 builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
1393 self.assertEqual([], builds)1417 self.assertEqual([], builds)
1394 expected_log_entries = [1418 expected_log_entries = [
1395 "DEBUG Scheduling builds of snap package %s/%s" % (1419 "DEBUG Scheduling builds of snap package %s/%s" % (
1396 snap.owner.name, snap.name),1420 snap.owner.name, snap.name),
1397 "WARNING - %s/%s/%s: An identical build of this snap package "
1398 "is already pending." % (
1399 snap.owner.name, snap.name, das.architecturetag),
1400 ]1421 ]
1401 self.assertEqual(1422 self.assertEqual(
1402 expected_log_entries, logger.getLogBuffer().splitlines())1423 expected_log_entries, logger.getLogBuffer().splitlines())
@@ -1410,7 +1431,11 @@
1410 distroarchseries=das,1431 distroarchseries=das,
1411 date_created=datetime.now(pytz.UTC) - timedelta(days=1),1432 date_created=datetime.now(pytz.UTC) - timedelta(days=1),
1412 status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=1))1433 status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=1))
1413 builds = getUtility(ISnapSet).makeAutoBuilds()1434 with GitHostingFixture(blob=dedent("""\
1435 architectures:
1436 - build-on: %s
1437 """) % das.architecturetag):
1438 builds = getUtility(ISnapSet).makeAutoBuilds()
1414 self.assertEqual(1, len(builds))1439 self.assertEqual(1, len(builds))
14151440
1416 def test_makeAutoBuilds_with_older_and_newer_builds(self):1441 def test_makeAutoBuilds_with_older_and_newer_builds(self):
@@ -1423,7 +1448,11 @@
1423 archive=snap.auto_build_archive, distroarchseries=das,1448 archive=snap.auto_build_archive, distroarchseries=das,
1424 date_created=datetime.now(pytz.UTC) - timediff,1449 date_created=datetime.now(pytz.UTC) - timediff,
1425 status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=1))1450 status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=1))
1426 self.assertEqual([], getUtility(ISnapSet).makeAutoBuilds())1451 with GitHostingFixture(blob=dedent("""\
1452 architectures:
1453 - build-on: %s
1454 """) % das.architecturetag):
1455 self.assertEqual([], getUtility(ISnapSet).makeAutoBuilds())
14271456
1428 def test_makeAutoBuilds_with_recent_build_from_different_archive(self):1457 def test_makeAutoBuilds_with_recent_build_from_different_archive(self):
1429 # If a snap package has been built recently but from an archive1458 # If a snap package has been built recently but from an archive
@@ -1434,9 +1463,49 @@
1434 requester=snap.owner, snap=snap, distroarchseries=das,1463 requester=snap.owner, snap=snap, distroarchseries=das,
1435 date_created=datetime.now(pytz.UTC) - timedelta(minutes=30),1464 date_created=datetime.now(pytz.UTC) - timedelta(minutes=30),
1436 status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=1))1465 status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=1))
1437 builds = getUtility(ISnapSet).makeAutoBuilds()1466 with GitHostingFixture(blob=dedent("""\
1467 architectures:
1468 - build-on: %s
1469 """) % das.architecturetag):
1470 builds = getUtility(ISnapSet).makeAutoBuilds()
1438 self.assertEqual(1, len(builds))1471 self.assertEqual(1, len(builds))
14391472
1473 def test_makeAutoBuilds_honours_explicit_architectures(self):
1474 # ISnapSet.makeAutoBuilds honours an explicit list of architectures
1475 # in the snap's snapcraft.yaml.
1476 das1, snap = self.makeAutoBuildableSnap(is_stale=True)
1477 das2 = self.makeBuildableDistroArchSeries(
1478 distroseries=das1.distroseries,
1479 processor=self.factory.makeProcessor(supports_virtualized=True))
1480 das3 = self.makeBuildableDistroArchSeries(
1481 distroseries=das1.distroseries,
1482 processor=self.factory.makeProcessor(supports_virtualized=True))
1483 snap.setProcessors([das1.processor, das2.processor, das3.processor])
1484 logger = BufferLogger()
1485 with GitHostingFixture(blob=dedent("""\
1486 architectures:
1487 - build-on: %s
1488 - build-on: %s
1489 """) % (das1.architecturetag, das2.architecturetag)):
1490 builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
1491 self.assertThat(set(builds), MatchesSetwise(*[
1492 MatchesStructure.byEquality(
1493 requester=snap.owner, snap=snap, distro_arch_series=das,
1494 status=BuildStatus.NEEDSBUILD)
1495 for das in (das1, das2)
1496 ]))
1497 expected_log_entries = [
1498 "DEBUG Scheduling builds of snap package %s/%s" % (
1499 snap.owner.name, snap.name),
1500 "DEBUG - %s/%s/%s: Build requested." % (
1501 snap.owner.name, snap.name, das1.architecturetag),
1502 "DEBUG - %s/%s/%s: Build requested." % (
1503 snap.owner.name, snap.name, das2.architecturetag),
1504 ]
1505 self.assertEqual(
1506 expected_log_entries, logger.getLogBuffer().splitlines())
1507 self.assertFalse(snap.is_stale)
1508
1440 def test_detachFromBranch(self):1509 def test_detachFromBranch(self):
1441 # ISnapSet.detachFromBranch clears the given Bazaar branch from all1510 # ISnapSet.detachFromBranch clears the given Bazaar branch from all
1442 # Snaps.1511 # Snaps.