launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #23279
[Merge] lp:~cjwatson/launchpad/snap-without-distro-series into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/snap-without-distro-series into lp:launchpad with lp:~cjwatson/launchpad/base-snap as a prerequisite.
Commit message:
Support building snaps without a configured distro series.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1812985 in Launchpad itself: "Support snapcraft base snaps"
https://bugs.launchpad.net/launchpad/+bug/1812985
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/snap-without-distro-series/+merge/362730
In this case, the series is inferred from the base snap given by snapcraft.yaml, if any.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/snap-without-distro-series into lp:launchpad.
=== modified file 'lib/lp/snappy/browser/snap.py'
--- lib/lp/snappy/browser/snap.py 2019-01-30 12:28:49 +0000
+++ lib/lp/snappy/browser/snap.py 2019-02-05 12:46:08 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2019 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Snap views."""
@@ -44,6 +44,7 @@
from lp.app.browser.tales import format_link
from lp.app.enums import PRIVATE_INFORMATION_TYPES
from lp.app.interfaces.informationtype import IInformationType
+from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.app.widgets.itemswidgets import (
LabeledMultiCheckBoxWidget,
LaunchpadDropdownWidget,
@@ -310,7 +311,13 @@
def initial_values(self):
"""See `LaunchpadFormView`."""
return {
- 'archive': self.context.distro_series.main_archive,
+ 'archive': (
+ # XXX cjwatson 2019-02-04: In order to support non-Ubuntu
+ # bases, we'd need to store this as None and guess it based
+ # on the guessed distro series; but this will do for now.
+ getUtility(ILaunchpadCelebrities).ubuntu.main_archive
+ if self.context.distro_series is None
+ else self.context.distro_series.main_archive),
'distro_arch_series': [],
'pocket': PackagePublishingPocket.UPDATES,
}
@@ -341,7 +348,7 @@
@action('Request builds', name='request')
def request_action(self, action, data):
- if data['distro_arch_series']:
+ if data.get('distro_arch_series', []):
builds, informational = self.requestBuild(data)
already_pending = informational.get('already_pending')
notification_text = new_builds_notification_text(
@@ -498,7 +505,13 @@
'processors': [
p for p in getUtility(IProcessorSet).getAll()
if p.build_by_default],
- 'auto_build_archive': distro_series.main_archive,
+ 'auto_build_archive': (
+ # XXX cjwatson 2019-02-04: In order to support non-Ubuntu
+ # bases, we'd need to store this as None and guess it based
+ # on the guessed distro series; but this will do for now.
+ getUtility(ILaunchpadCelebrities).ubuntu.main_archive
+ if distro_series is None
+ else distro_series.main_archive),
'auto_build_pocket': PackagePublishingPocket.UPDATES,
}
=== modified file 'lib/lp/snappy/browser/tests/test_snap.py'
--- lib/lp/snappy/browser/tests/test_snap.py 2019-01-30 12:28:49 +0000
+++ lib/lp/snappy/browser/tests/test_snap.py 2019-02-05 12:46:08 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2019 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Test snap package views."""
@@ -1173,11 +1173,13 @@
self.factory.makeBuilder(virtualized=True)
def makeSnap(self, **kwargs):
+ if "distroseries" not in kwargs:
+ kwargs["distroseries"] = self.distroseries
if kwargs.get("branch") is None and kwargs.get("git_ref") is None:
kwargs["branch"] = self.factory.makeAnyBranch()
return self.factory.makeSnap(
- registrant=self.person, owner=self.person,
- distroseries=self.distroseries, name="snap-name", **kwargs)
+ registrant=self.person, owner=self.person, name="snap-name",
+ **kwargs)
def makeBuild(self, snap=None, archive=None, date_created=None, **kwargs):
if snap is None:
@@ -1299,6 +1301,15 @@
Primary Archive for Ubuntu Linux
""", self.getMainText(build.snap))
+ def test_index_no_distro_series(self):
+ # If the snap is configured to guess an appropriate distro series
+ # from snapcraft.yaml, then the index page does not show a distro
+ # series.
+ snap = self.makeSnap(distroseries=None)
+ text = self.getMainText(snap)
+ self.assertIn("Snap package information", text)
+ self.assertNotIn("Distribution series:", text)
+
def test_index_success_with_buildlog(self):
# The build log is shown if it is there.
build = self.makeBuild(
@@ -1383,6 +1394,8 @@
store_upload_tag = soupmatchers.Tag(
"store upload", "div", attrs={"id": "store_upload"})
self.assertThat(view(), soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ "distribution series", "dl", attrs={"id": "distro_series"}),
soupmatchers.Within(
store_upload_tag,
soupmatchers.Tag(
@@ -1629,3 +1642,25 @@
login_person(self.person)
[request] = self.snap.pending_build_requests
self.assertEqual(ppa, request.archive)
+
+ def test_request_builds_no_distro_series(self):
+ # Requesting builds of a snap configured to guess an appropriate
+ # distro series from snapcraft.yaml creates a pending build request.
+ login_person(self.person)
+ self.snap.distro_series = None
+ browser = self.getViewBrowser(
+ self.snap, "+request-builds", user=self.person)
+ browser.getControl("Request builds").click()
+
+ login_person(self.person)
+ [request] = self.snap.pending_build_requests
+ self.assertThat(removeSecurityProxy(request), MatchesStructure(
+ snap=Equals(self.snap),
+ status=Equals(SnapBuildRequestStatus.PENDING),
+ error_message=Is(None),
+ builds=AfterPreprocessing(list, Equals([])),
+ archive=Equals(self.ubuntu.main_archive),
+ _job=MatchesStructure(
+ requester=Equals(self.person),
+ pocket=Equals(PackagePublishingPocket.UPDATES),
+ channels=Is(None))))
=== modified file 'lib/lp/snappy/browser/widgets/snaparchive.py'
--- lib/lp/snappy/browser/widgets/snaparchive.py 2016-06-20 20:47:20 +0000
+++ lib/lp/snappy/browser/widgets/snaparchive.py 2019-02-05 12:46:08 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2019 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -70,7 +70,8 @@
@property
def main_archive(self):
- if ISnap.providedBy(self.context.context):
+ if (ISnap.providedBy(self.context.context) and
+ self.context.context.distro_series is not None):
return self.context.context.distro_series.main_archive
else:
return getUtility(ILaunchpadCelebrities).ubuntu.main_archive
=== modified file 'lib/lp/snappy/browser/widgets/tests/test_snaparchivewidget.py'
--- lib/lp/snappy/browser/widgets/tests/test_snaparchivewidget.py 2017-11-10 11:28:43 +0000
+++ lib/lp/snappy/browser/widgets/tests/test_snaparchivewidget.py 2019-02-05 12:46:08 +0000
@@ -1,10 +1,11 @@
-# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2019 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
+from functools import partial
import re
from lazr.restful.fields import Reference
@@ -36,8 +37,10 @@
from lp.testing.layers import DatabaseFunctionalLayer
-def make_snap(test_case):
- return test_case.factory.makeSnap(distroseries=test_case.distroseries)
+def make_snap(test_case, **kwargs):
+ if "distroseries" not in kwargs:
+ kwargs["distroseries"] = test_case.distroseries
+ return test_case.factory.makeSnap(**kwargs)
def make_branch(test_case):
@@ -54,6 +57,8 @@
scenarios = [
("Snap", {"context_factory": make_snap}),
+ ("Snap with no distroseries",
+ {"context_factory": partial(make_snap, distroseries=None)}),
("Branch", {"context_factory": make_branch}),
("GitRepository", {"context_factory": make_git_repository}),
]
@@ -219,12 +224,13 @@
def test_getInputValue_primary(self):
# When the primary radio button is selected, the field value is the
- # context's primary archive if the context is a Snap, or the Ubuntu
- # primary archive otherwise.
+ # context's primary archive if the context is a Snap and has a
+ # distroseries, or the Ubuntu primary archive otherwise.
self.widget.request = LaunchpadTestRequest(
form={"field.archive": "primary"})
- if ISnap.providedBy(self.context):
- expected_main_archive = self.distroseries.main_archive
+ if (ISnap.providedBy(self.context) and
+ self.context.distro_series is not None):
+ expected_main_archive = self.context.distro_series.main_archive
else:
expected_main_archive = (
getUtility(ILaunchpadCelebrities).ubuntu.main_archive)
=== modified file 'lib/lp/snappy/interfaces/snap.py'
--- lib/lp/snappy/interfaces/snap.py 2018-11-07 18:12:08 +0000
+++ lib/lp/snappy/interfaces/snap.py 2019-02-05 12:46:08 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2019 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Snap package interfaces."""
@@ -153,10 +153,10 @@
class SnapBuildDisallowedArchitecture(Exception):
"""A build was requested for a disallowed architecture."""
- def __init__(self, das):
+ def __init__(self, das, pocket):
super(SnapBuildDisallowedArchitecture, self).__init__(
- "This snap package is not allowed to build for %s." %
- das.displayname)
+ "This snap package is not allowed to build for %s/%s." %
+ (das.distroseries.getSuite(pocket), das.architecturetag))
@error_status(httplib.UNAUTHORIZED)
@@ -453,6 +453,8 @@
:param build_request: The `ISnapBuildRequest` job being processed,
if any.
:param logger: An optional logger.
+ :raises CannotRequestAutoBuilds: if fetch_snapcraft_yaml is False
+ and self.distro_series is not set.
:return: A sequence of `ISnapBuild` instances.
"""
@@ -547,6 +549,10 @@
logger=None):
"""Create and return automatic builds for this snap package.
+ This webservice API method is deprecated. It is normally better to
+ use the `requestBuilds` method instead, which can make dispatching
+ decisions based on the contents of snapcraft.yaml.
+
:param allow_failures: If True, log exceptions other than "already
pending" from individual build requests; if False, raise them to
the caller.
@@ -557,6 +563,7 @@
:param logger: An optional logger.
:raises CannotRequestAutoBuilds: if no auto_build_archive or
auto_build_pocket is set.
+ :raises IncompatibleArguments: if no distro_series is set.
:return: A sequence of `ISnapBuild` instances.
"""
@@ -632,9 +639,11 @@
description=_("The owner of this snap package.")))
distro_series = exported(Reference(
- IDistroSeries, title=_("Distro Series"), required=True, readonly=False,
+ IDistroSeries, title=_("Distro Series"),
+ required=False, readonly=False,
description=_(
- "The series for which the snap package should be built.")))
+ "The series for which the snap package should be built. If not "
+ "set, Launchpad will guess at an appropriate series.")))
name = exported(TextLine(
title=_("Name"), required=True, readonly=False,
=== modified file 'lib/lp/snappy/model/snap.py'
--- lib/lp/snappy/model/snap.py 2019-02-05 12:46:08 +0000
+++ lib/lp/snappy/model/snap.py 2019-02-05 12:46:08 +0000
@@ -93,6 +93,8 @@
IHasOwner,
IPersonRoles,
)
+from lp.registry.model.distroseries import DistroSeries
+from lp.registry.model.series import ACTIVE_STATUSES
from lp.registry.model.teammembership import TeamParticipation
from lp.services.config import config
from lp.services.database.bulk import load_related
@@ -129,6 +131,10 @@
from lp.services.webhooks.interfaces import IWebhookSet
from lp.services.webhooks.model import WebhookTargetMixin
from lp.snappy.adapters.buildarch import determine_architectures_to_build
+from lp.snappy.interfaces.basesnap import (
+ IBaseSnapSet,
+ NoSuchBaseSnap,
+ )
from lp.snappy.interfaces.snap import (
BadSnapSearchContext,
BadSnapSource,
@@ -258,7 +264,7 @@
owner_id = Int(name='owner', allow_none=False)
owner = Reference(owner_id, 'Person.id')
- distro_series_id = Int(name='distro_series', allow_none=False)
+ distro_series_id = Int(name='distro_series', allow_none=True)
distro_series = Reference(distro_series_id, 'DistroSeries.id')
name = Unicode(name='name', allow_none=False)
@@ -393,13 +399,21 @@
@property
def available_processors(self):
"""See `ISnap`."""
- processors = Store.of(self).find(
- Processor,
- Processor.id == DistroArchSeries.processor_id,
- DistroArchSeries.id.is_in(
+ clauses = [Processor.id == DistroArchSeries.processor_id]
+ if self.distro_series is not None:
+ clauses.append(DistroArchSeries.id.is_in(
self.distro_series.enabled_architectures.get_select_expr(
DistroArchSeries.id)))
- return processors.config(distinct=True)
+ else:
+ # We don't know the series until we've looked at snapcraft.yaml
+ # to dispatch a build, so enabled architectures for any active
+ # series will do.
+ clauses.extend([
+ DistroArchSeries.enabled == True,
+ DistroArchSeries.distroseriesID == DistroSeries.id,
+ DistroSeries.status.is_in(ACTIVE_STATUSES),
+ ])
+ return Store.of(self).find(Processor, *clauses).config(distinct=True)
def _getProcessors(self):
return list(Store.of(self).find(
@@ -445,16 +459,32 @@
processors = property(_getProcessors, setProcessors)
- def getAllowedArchitectures(self):
+ def _isBuildableArchitectureAllowed(self, das):
+ """Check whether we may build for a buildable `DistroArchSeries`.
+
+ The caller is assumed to have already checked that a suitable chroot
+ is available (either directly or via
+ `DistroSeries.buildable_architectures`).
+ """
+ return (
+ das.enabled
+ and das.processor in self.processors
+ and (
+ das.processor.supports_virtualized
+ or not self.require_virtualized))
+
+ def _isArchitectureAllowed(self, das, pocket):
+ return (
+ das.getChroot(pocket=pocket) is not None
+ and self._isBuildableArchitectureAllowed(das))
+
+ def getAllowedArchitectures(self, distro_series=None):
"""See `ISnap`."""
+ if distro_series is None:
+ distro_series = self.distro_series
return [
- das for das in self.distro_series.buildable_architectures
- if (
- das.enabled
- and das.processor in self.processors
- and (
- das.processor.supports_virtualized
- or not self.require_virtualized))]
+ das for das in distro_series.buildable_architectures
+ if self._isBuildableArchitectureAllowed(das)]
@property
def store_distro_series(self):
@@ -559,8 +589,8 @@
channels=None, build_request=None):
"""See `ISnap`."""
self._checkRequestBuild(requester, archive)
- if distro_arch_series not in self.getAllowedArchitectures():
- raise SnapBuildDisallowedArchitecture(distro_arch_series)
+ if not self._isArchitectureAllowed(distro_arch_series, pocket):
+ raise SnapBuildDisallowedArchitecture(distro_arch_series, pocket)
pending = IStore(self).find(
SnapBuild,
@@ -591,6 +621,10 @@
allow_failures=False, fetch_snapcraft_yaml=True,
build_request=None, logger=None):
"""See `ISnap`."""
+ if not fetch_snapcraft_yaml and self.distro_series is None:
+ # Slightly misleading, but requestAutoBuilds is the only place
+ # this can happen right now and it raises a more specific error.
+ raise CannotRequestAutoBuilds("distro_series")
try:
if fetch_snapcraft_yaml:
try:
@@ -606,12 +640,38 @@
snapcraft_data = {}
else:
snapcraft_data = {}
+
+ # Find a suitable base snap.
+ base_snap_set = getUtility(IBaseSnapSet)
+ if "base" in snapcraft_data:
+ base_snap_name = snapcraft_data["base"]
+ if isinstance(base_snap_name, bytes):
+ base_snap_name = base_snap_name.decode("UTF-8")
+ base_snap = base_snap_set.getByName(base_snap_name)
+ else:
+ base_snap = base_snap_set.getDefault()
+
+ # Combine the base snap with other configuration to find a
+ # suitable distro series and suitable channels.
+ distro_series = self.distro_series
+ if base_snap is not None:
+ if distro_series is None:
+ distro_series = base_snap.distro_series
+ new_channels = dict(base_snap.channels)
+ if channels is not None:
+ new_channels.update(channels)
+ channels = new_channels
+ elif distro_series is None:
+ # A base snap is mandatory if there's no configured distro
+ # series.
+ raise NoSuchBaseSnap(snapcraft_data.get("base", "<default>"))
+
# Sort by Processor.id for determinism. This is chosen to be
# the same order as in BinaryPackageBuildSet.createForSource, to
# minimise confusion.
supported_arches = OrderedDict(
(das.architecturetag, das) for das in sorted(
- self.getAllowedArchitectures(),
+ self.getAllowedArchitectures(distro_series),
key=attrgetter("processor.id")))
architectures_to_build = determine_architectures_to_build(
snapcraft_data, supported_arches.keys())
@@ -652,6 +712,11 @@
raise CannotRequestAutoBuilds("auto_build_archive")
if self.auto_build_pocket is None:
raise CannotRequestAutoBuilds("auto_build_pocket")
+ if self.distro_series is None:
+ raise IncompatibleArguments(
+ "Cannot use requestAutoBuilds for a snap package without "
+ "distro_series being set. Consider using requestBuilds "
+ "instead.")
self.is_stale = False
if logger is not None:
logger.debug(
=== modified file 'lib/lp/snappy/templates/snap-index.pt'
--- lib/lp/snappy/templates/snap-index.pt 2018-09-27 13:51:42 +0000
+++ lib/lp/snappy/templates/snap-index.pt 2019-02-05 12:46:08 +0000
@@ -45,9 +45,11 @@
<dt>Owner:</dt>
<dd tal:content="structure view/person_picker"/>
</dl>
- <dl id="distro_series">
+ <dl id="distro_series"
+ tal:define="distro_series context/distro_series"
+ tal:condition="distro_series">
<dt>Distribution series:</dt>
- <dd tal:define="distro_series context/distro_series">
+ <dd>
<a tal:attributes="href distro_series/fmt:url"
tal:content="distro_series/fullseriesname"/>
<a tal:replace="structure view/menu:overview/edit/fmt:icon"/>
=== modified file 'lib/lp/snappy/templates/snap-request-builds.pt'
--- lib/lp/snappy/templates/snap-request-builds.pt 2015-09-18 13:32:09 +0000
+++ lib/lp/snappy/templates/snap-request-builds.pt 2019-02-05 12:46:08 +0000
@@ -14,9 +14,17 @@
<tal:widget define="widget nocall:view/widgets/archive">
<metal:block use-macro="context/@@launchpad_form/widget_row" />
</tal:widget>
- <tal:widget define="widget nocall:view/widgets/distro_arch_series">
- <metal:block use-macro="context/@@launchpad_form/widget_row" />
- </tal:widget>
+ <tal:comment condition="nothing">
+ XXX cjwatson 2019-02-05: Architecture selection is still useful
+ even when the series is guessed from snapcraft.yaml, but it's more
+ difficult to work out what choices to offer. For now we just make
+ it all-or-nothing in that case.
+ </tal:comment>
+ <tal:has-distro-series condition="context/distro_series">
+ <tal:widget define="widget nocall:view/widgets/distro_arch_series">
+ <metal:block use-macro="context/@@launchpad_form/widget_row" />
+ </tal:widget>
+ </tal:has-distro-series>
<tal:widget define="widget nocall:view/widgets/pocket">
<metal:block use-macro="context/@@launchpad_form/widget_row" />
</tal:widget>
=== modified file 'lib/lp/snappy/tests/test_snap.py'
--- lib/lp/snappy/tests/test_snap.py 2019-02-05 12:46:08 +0000
+++ lib/lp/snappy/tests/test_snap.py 2019-02-05 12:46:08 +0000
@@ -62,6 +62,7 @@
from lp.registry.enums import PersonVisibility
from lp.registry.interfaces.distribution import IDistributionSet
from lp.registry.interfaces.pocket import PackagePublishingPocket
+from lp.registry.interfaces.series import SeriesStatus
from lp.services.config import config
from lp.services.database.constants import (
ONE_DAY_AGO,
@@ -84,6 +85,10 @@
from lp.services.webapp.interfaces import OAuthPermission
from lp.services.webapp.publisher import canonical_url
from lp.services.webapp.snapshot import notify_modified
+from lp.snappy.interfaces.basesnap import (
+ IBaseSnapSet,
+ NoSuchBaseSnap,
+ )
from lp.snappy.interfaces.snap import (
BadSnapSearchContext,
CannotFetchSnapcraftYaml,
@@ -326,6 +331,22 @@
SnapBuildDisallowedArchitecture, snap.requestBuild,
snap.owner, snap.distro_series.main_archive, arches[1],
PackagePublishingPocket.UPDATES)
+ inactive_proc = self.factory.makeProcessor(supports_virtualized=True)
+ inactive_arch = self.makeBuildableDistroArchSeries(
+ distroseries=self.factory.makeDistroSeries(
+ status=SeriesStatus.OBSOLETE),
+ processor=inactive_proc)
+ snap_no_ds = self.factory.makeSnap(distroseries=None)
+ snap_no_ds.requestBuild(
+ snap_no_ds.owner, distroseries.main_archive, arches[0],
+ PackagePublishingPocket.UPDATES)
+ snap_no_ds.requestBuild(
+ snap_no_ds.owner, distroseries.main_archive, arches[1],
+ PackagePublishingPocket.UPDATES)
+ self.assertRaises(
+ SnapBuildDisallowedArchitecture, snap.requestBuild,
+ snap.owner, snap.distro_series.main_archive, inactive_arch,
+ PackagePublishingPocket.UPDATES)
def test_requestBuild_virtualization(self):
# New builds are virtualized if any of the processor, snap or
@@ -439,6 +460,33 @@
pocket=Equals(PackagePublishingPocket.UPDATES),
channels=Equals({"snapcraft": "edge"})))
+ def test_requestBuilds_without_distroseries(self):
+ # requestBuilds schedules a job for a snap without a distroseries.
+ snap = self.factory.makeSnap(distroseries=None)
+ archive = self.factory.makeArchive()
+ now = get_transaction_timestamp(IStore(snap))
+ with person_logged_in(snap.owner.teamowner):
+ request = snap.requestBuilds(
+ snap.owner.teamowner, archive, PackagePublishingPocket.UPDATES,
+ channels={"snapcraft": "edge"})
+ self.assertThat(request, MatchesStructure(
+ date_requested=Equals(now),
+ date_finished=Is(None),
+ snap=Equals(snap),
+ status=Equals(SnapBuildRequestStatus.PENDING),
+ error_message=Is(None),
+ builds=AfterPreprocessing(set, MatchesSetwise()),
+ archive=Equals(archive)))
+ [job] = getUtility(ISnapRequestBuildsJobSource).iterReady()
+ self.assertThat(job, MatchesStructure(
+ job_id=Equals(request.id),
+ job=MatchesStructure.byEquality(status=JobStatus.WAITING),
+ snap=Equals(snap),
+ requester=Equals(snap.owner.teamowner),
+ archive=Equals(archive),
+ pocket=Equals(PackagePublishingPocket.UPDATES),
+ channels=Equals({"snapcraft": "edge"})))
+
def makeRequestBuildsJob(self, arch_tags, git_ref=None):
distro = self.factory.makeDistribution()
distroseries = self.factory.makeDistroSeries(distribution=distro)
@@ -447,11 +495,9 @@
name=arch_tag, supports_virtualized=True)
for arch_tag in arch_tags]
for processor in processors:
- das = self.factory.makeDistroArchSeries(
+ self.makeBuildableDistroArchSeries(
distroseries=distroseries, architecturetag=processor.name,
processor=processor)
- das.addOrUpdateChroot(self.factory.makeLibraryFileAlias(
- filename="fake_chroot.tar.gz", db_only=True))
if git_ref is None:
[git_ref] = self.factory.makeGitRefs()
snap = self.factory.makeSnap(
@@ -460,15 +506,18 @@
snap, snap.owner.teamowner, distro.main_archive,
PackagePublishingPocket.RELEASE, {"snapcraft": "edge"})
- def assertRequestedBuildsMatch(self, builds, job, arch_tags):
+ def assertRequestedBuildsMatch(self, builds, job, arch_tags, channels,
+ distro_series=None):
+ if distro_series is None:
+ distro_series = job.snap.distro_series
self.assertThat(builds, MatchesSetwise(
*(MatchesStructure(
requester=Equals(job.requester),
snap=Equals(job.snap),
archive=Equals(job.archive),
- distro_arch_series=Equals(job.snap.distro_series[arch_tag]),
+ distro_arch_series=Equals(distro_series[arch_tag]),
pocket=Equals(job.pocket),
- channels=Equals(job.channels))
+ channels=Equals(channels))
for arch_tag in arch_tags)))
def test_requestBuildsFromJob_restricts_explicit_list(self):
@@ -489,7 +538,7 @@
job.requester, job.archive, job.pocket,
removeSecurityProxy(job.channels),
build_request=job.build_request)
- self.assertRequestedBuildsMatch(builds, job, ["sparc"])
+ self.assertRequestedBuildsMatch(builds, job, ["sparc"], job.channels)
def test_requestBuildsFromJob_no_explicit_architectures(self):
# If the snap doesn't specify any architectures,
@@ -505,7 +554,90 @@
job.requester, job.archive, job.pocket,
removeSecurityProxy(job.channels),
build_request=job.build_request)
- self.assertRequestedBuildsMatch(builds, job, ["mips64el", "riscv64"])
+ self.assertRequestedBuildsMatch(
+ builds, job, ["mips64el", "riscv64"], job.channels)
+
+ def test_requestBuildsFromJob_no_distroseries_explicit_base(self):
+ # If the snap doesn't specify a distroseries but has an explicit
+ # base, requestBuildsFromJob requests builds for the appropriate
+ # distroseries for the base.
+ self.useFixture(GitHostingFixture(blob="base: test-base\n"))
+ with admin_logged_in():
+ base_snap = self.factory.makeBaseSnap(
+ name="test-base",
+ channels={"snapcraft": "stable/launchpad-buildd"})
+ self.factory.makeBaseSnap()
+ for arch_tag in ("mips64el", "riscv64"):
+ self.makeBuildableDistroArchSeries(
+ distroseries=base_snap.distro_series, architecturetag=arch_tag,
+ processor=self.factory.makeProcessor(
+ name=arch_tag, supports_virtualized=True))
+ snap = self.factory.makeSnap(
+ distroseries=None, git_ref=self.factory.makeGitRefs()[0])
+ job = getUtility(ISnapRequestBuildsJobSource).create(
+ snap, snap.owner.teamowner, base_snap.distro_series.main_archive,
+ PackagePublishingPocket.RELEASE, None)
+ self.assertEqual(
+ get_transaction_timestamp(IStore(snap)), job.date_created)
+ transaction.commit()
+ with person_logged_in(job.requester):
+ builds = snap.requestBuildsFromJob(
+ job.requester, job.archive, job.pocket,
+ build_request=job.build_request)
+ self.assertRequestedBuildsMatch(
+ builds, job, ["mips64el", "riscv64"], base_snap.channels,
+ distro_series=base_snap.distro_series)
+
+ def test_requestBuildsFromJob_no_distroseries_no_explicit_base(self):
+ # If the snap doesn't specify a distroseries and has no explicit
+ # base, requestBuildsFromJob requests builds for the appropriate
+ # distroseries for the default base snap.
+ self.useFixture(GitHostingFixture(blob="name: foo\n"))
+ with admin_logged_in():
+ base_snap = self.factory.makeBaseSnap(
+ channels={"snapcraft": "stable/launchpad-buildd"})
+ getUtility(IBaseSnapSet).setDefault(base_snap)
+ self.factory.makeBaseSnap()
+ for arch_tag in ("mips64el", "riscv64"):
+ self.makeBuildableDistroArchSeries(
+ distroseries=base_snap.distro_series, architecturetag=arch_tag,
+ processor=self.factory.makeProcessor(
+ name=arch_tag, supports_virtualized=True))
+ snap = self.factory.makeSnap(
+ distroseries=None, git_ref=self.factory.makeGitRefs()[0])
+ job = getUtility(ISnapRequestBuildsJobSource).create(
+ snap, snap.owner.teamowner, base_snap.distro_series.main_archive,
+ PackagePublishingPocket.RELEASE, None)
+ self.assertEqual(
+ get_transaction_timestamp(IStore(snap)), job.date_created)
+ transaction.commit()
+ with person_logged_in(job.requester):
+ builds = snap.requestBuildsFromJob(
+ job.requester, job.archive, job.pocket,
+ build_request=job.build_request)
+ self.assertRequestedBuildsMatch(
+ builds, job, ["mips64el", "riscv64"], base_snap.channels,
+ distro_series=base_snap.distro_series)
+
+ def test_requestBuildsFromJob_no_distroseries_no_default_base(self):
+ # If the snap doesn't specify a distroseries and has an explicit
+ # base, and there is no default base snap, requestBuildsFromJob
+ # gives up.
+ self.useFixture(GitHostingFixture(blob="name: foo\n"))
+ with admin_logged_in():
+ base_snap = self.factory.makeBaseSnap(
+ channels={"snapcraft": "stable/launchpad-buildd"})
+ snap = self.factory.makeSnap(
+ distroseries=None, git_ref=self.factory.makeGitRefs()[0])
+ job = getUtility(ISnapRequestBuildsJobSource).create(
+ snap, snap.owner.teamowner, base_snap.distro_series.main_archive,
+ PackagePublishingPocket.RELEASE, None)
+ transaction.commit()
+ with person_logged_in(job.requester):
+ self.assertRaises(
+ NoSuchBaseSnap, snap.requestBuildsFromJob,
+ job.requester, job.archive, job.pocket,
+ build_request=job.build_request)
def test_requestBuildsFromJob_unsupported_remote(self):
# If the snap is based on an external Git repository from which we
@@ -523,7 +655,8 @@
job.requester, job.archive, job.pocket,
removeSecurityProxy(job.channels),
build_request=job.build_request)
- self.assertRequestedBuildsMatch(builds, job, ["mips64el", "riscv64"])
+ self.assertRequestedBuildsMatch(
+ builds, job, ["mips64el", "riscv64"], job.channels)
def test_requestBuildsFromJob_triggers_webhooks(self):
# requestBuildsFromJob triggers webhooks, and the payload includes a
@@ -1911,6 +2044,29 @@
self.arm = self.factory.makeProcessor(
name="arm", restricted=True, build_by_default=False)
+ def test_available_processors_with_distro_series(self):
+ # If the snap has a distroseries, only those processors that are
+ # enabled for that series are available.
+ distroseries = self.factory.makeDistroSeries()
+ for processor in self.default_procs:
+ self.factory.makeDistroArchSeries(
+ distroseries=distroseries, architecturetag=processor.name,
+ processor=processor)
+ self.factory.makeDistroArchSeries(
+ architecturetag=self.arm.name, processor=self.arm)
+ snap = self.factory.makeSnap(distroseries=distroseries)
+ self.assertContentEqual(self.default_procs, snap.available_processors)
+
+ def test_available_processors_without_distro_series(self):
+ # If the snap does not have a distroseries, then processors that are
+ # enabled for any active series are available.
+ snap = self.factory.makeSnap(distroseries=None)
+ # 386 and hppa have corresponding DASes in sampledata for active
+ # distroseries.
+ self.assertContentEqual(
+ ["386", "hppa"],
+ [processor.name for processor in snap.available_processors])
+
def test_new_default_processors(self):
# SnapSet.new creates a SnapArch for each available Processor with
# build_by_default set.
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2019-02-05 12:46:08 +0000
+++ lib/lp/testing/factory.py 2019-02-05 12:46:08 +0000
@@ -4711,7 +4711,7 @@
target, self.makePerson(), delivery_url, event_types or [],
active, secret)
- def makeSnap(self, registrant=None, owner=None, distroseries=None,
+ def makeSnap(self, registrant=None, owner=None, distroseries=_DEFAULT,
name=None, branch=None, git_ref=None, auto_build=False,
auto_build_archive=None, auto_build_pocket=None,
auto_build_channels=None, is_stale=None,
@@ -4725,7 +4725,7 @@
registrant = self.makePerson()
if owner is None:
owner = self.makeTeam(registrant)
- if distroseries is None:
+ if distroseries is _DEFAULT:
distroseries = self.makeDistroSeries()
if name is None:
name = self.getUniqueString(u"snap-name")
@@ -4785,7 +4785,7 @@
distroseries = self.makeDistroSeries(
distribution=archive.distribution)
else:
- distroseries = None
+ distroseries = _DEFAULT
if registrant is None:
registrant = requester
snap = self.makeSnap(
Follow ups