Merge ~cjwatson/launchpad:built-using-model into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 4f4c8809bc92c7ef495d8281b0fbac9cd2d7bc92
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:built-using-model
Merge into: launchpad:master
Diff against target: 1100 lines (+678/-24)
18 files modified
database/schema/security.cfg (+2/-0)
lib/lp/archiveuploader/nascentuploadfile.py (+8/-1)
lib/lp/archiveuploader/tests/test_nascentuploadfile.py (+29/-1)
lib/lp/registry/model/distroseries.py (+21/-1)
lib/lp/soyuz/configure.zcml (+14/-0)
lib/lp/soyuz/doc/distroarchseriesbinarypackage.txt (+2/-0)
lib/lp/soyuz/enums.py (+14/-1)
lib/lp/soyuz/interfaces/binarypackagebuild.py (+3/-3)
lib/lp/soyuz/interfaces/binarypackagerelease.py (+6/-1)
lib/lp/soyuz/interfaces/binarysourcereference.py (+86/-0)
lib/lp/soyuz/model/binarypackagebuild.py (+13/-4)
lib/lp/soyuz/model/binarypackagerelease.py (+15/-1)
lib/lp/soyuz/model/binarysourcereference.py (+149/-0)
lib/lp/soyuz/scripts/gina/handlers.py (+16/-1)
lib/lp/soyuz/scripts/gina/packages.py (+10/-1)
lib/lp/soyuz/scripts/tests/test_gina.py (+73/-4)
lib/lp/soyuz/tests/test_binarysourcereference.py (+208/-0)
lib/lp/soyuz/tests/test_publishing.py (+9/-5)
Reviewer Review Type Date Requested Status
Tom Wardill (community) Approve
William Grant direction Approve
Review via email: mp+381234@code.launchpad.net

Commit message

Parse Built-Using fields from uploaded binaries

Description of the change

These are parsed into a form that we'll later be able to use in the dominator, with the goal of keeping source publications that are referenced by Built-Using fields in active binary publications.

The most complicated bit is working out which source package releases a given Built-Using field refers to, since there are edge cases where just the name and version can be ambiguous. We do the best we can by following the archive dependencies of the build and finding sources that it could plausibly have used.

Unresolvable entries in Built-Using, including ones that refer to deleted source publications, will result in binary uploads being rejected.

Database MP: https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/381036

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (direction)
de9e46b... by Colin Watson

Explicitly check "build.current_component is None"

This can happen in tests, especially of gina, and the resulting crash is
a bit confusing, so let's raise a clearer exception in this case.

fde0f10... by Colin Watson

Preserve original Built-Using fields for publication

We aren't sure whether apt will get confused if the exact serialisation
(e.g. ordering) of Built-Using in Packages differs from that in the
package's control file, so let's preserve it rather than trying to
reconstruct it.

0692e86... by Colin Watson

Don't consider SPPH.status when creating BSRs

We don't normally treat SUPERSEDED differently from DELETED in this sort
of situation, and at that point it isn't really worth checking the
status at all.

a4163e3... by Colin Watson

Add more vertical whitespace

7ca1f32... by Colin Watson

Limit Built-Using references to the same archive and series

Allowing cross-archive references makes the dominator's job harder, as
well as permitting non-consensual pinning of sources to the Published
state in foreign archives. Forbidding such references may be confusing
in some cases, but seems likely to be less bad than the alternative.

8983670... by Colin Watson

Refactor createFromRelationship using Archive.getPublishedSources

Now that we're only considering a single archive, this is equivalent and
much clearer.

e9bb481... by Colin Watson

Add BinarySourceReference.createFromSourcePackageReleases

This makes some tests (especially in branches further up this stack)
more convenient.

Revision history for this message
Tom Wardill (twom) wrote :

What is the possible 'unfortunate apt behaviour'?

review: Needs Information
6fd4090... by Colin Watson

Clarify "unfortunate apt behaviour"

4f4c880... by Colin Watson

Give IBinaryPackageRelease.built_using_references a value_type

Revision history for this message
Colin Watson (cjwatson) wrote :

I've clarified this.

Revision history for this message
Tom Wardill (twom) wrote :

The model and implementation for this seem to make sense. I'm not entirely fluent in apt behaviours, but this LGTM :).

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/database/schema/security.cfg b/database/schema/security.cfg
2index a0cf410..5ead0b8 100644
3--- a/database/schema/security.cfg
4+++ b/database/schema/security.cfg
5@@ -1213,6 +1213,7 @@ public.binarypackagefile = SELECT, INSERT, UPDATE
6 public.binarypackagename = SELECT, INSERT, UPDATE
7 public.binarypackagepublishinghistory = SELECT, INSERT, UPDATE, DELETE
8 public.binarypackagerelease = SELECT, INSERT, UPDATE
9+public.binarysourcereference = SELECT, INSERT
10 public.branch = SELECT, INSERT, UPDATE
11 public.bug = SELECT, INSERT, UPDATE
12 public.bugactivity = SELECT, INSERT, UPDATE
13@@ -1385,6 +1386,7 @@ public.binarypackagefile = SELECT, INSERT
14 public.binarypackagename = SELECT, INSERT
15 public.binarypackagepublishinghistory = SELECT
16 public.binarypackagerelease = SELECT, INSERT
17+public.binarysourcereference = SELECT, INSERT
18 public.bug = SELECT, UPDATE
19 public.bugactivity = SELECT, INSERT
20 public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE
21diff --git a/lib/lp/archiveuploader/nascentuploadfile.py b/lib/lp/archiveuploader/nascentuploadfile.py
22index aa0ec95..f28e43e 100644
23--- a/lib/lp/archiveuploader/nascentuploadfile.py
24+++ b/lib/lp/archiveuploader/nascentuploadfile.py
25@@ -1,4 +1,4 @@
26-# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
27+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
28 # GNU Affero General Public License version 3 (see the file LICENSE).
29
30 """Specific models for uploaded files"""
31@@ -445,6 +445,12 @@ class BaseBinaryUploadFile(PackageUploadFile):
32 "Provides",
33 "Pre-Depends",
34 "Enhances",
35+ # Note that we intentionally don't include Built-Using here;
36+ # although we parse it, we want to preserve its original form to
37+ # make sure apt doesn't decide that it needs to keep re-upgrading
38+ # the package to the same version because the metadata looks
39+ # slightly out of sync. This is most easily done by adding it to
40+ # user_defined_fields.
41 "Essential",
42 "Description",
43 "Installed-Size",
44@@ -920,6 +926,7 @@ class BaseBinaryUploadFile(PackageUploadFile):
45 pre_depends=encoded.get('Pre-Depends', ''),
46 enhances=encoded.get('Enhances', ''),
47 breaks=encoded.get('Breaks', ''),
48+ built_using=encoded.get('Built-Using', ''),
49 homepage=encoded.get('Homepage'),
50 essential=is_essential,
51 installedsize=installedsize,
52diff --git a/lib/lp/archiveuploader/tests/test_nascentuploadfile.py b/lib/lp/archiveuploader/tests/test_nascentuploadfile.py
53index 54841a9..ff63636 100644
54--- a/lib/lp/archiveuploader/tests/test_nascentuploadfile.py
55+++ b/lib/lp/archiveuploader/tests/test_nascentuploadfile.py
56@@ -1,4 +1,4 @@
57-# Copyright 2010-2019 Canonical Ltd. This software is licensed under the
58+# Copyright 2010-2020 Canonical Ltd. This software is licensed under the
59 # GNU Affero General Public License version 3 (see the file LICENSE).
60
61 """Test NascentUploadFile functionality."""
62@@ -26,6 +26,8 @@ from testtools.matchers import (
63 MatchesAny,
64 MatchesListwise,
65 MatchesRegex,
66+ MatchesSetwise,
67+ MatchesStructure,
68 )
69
70 from lp.archiveuploader.changesfile import ChangesFile
71@@ -43,6 +45,7 @@ from lp.services.compat import lzma
72 from lp.services.log.logger import BufferLogger
73 from lp.services.osutils import write_file
74 from lp.soyuz.enums import (
75+ BinarySourceReferenceType,
76 PackagePublishingStatus,
77 PackageUploadCustomFormat,
78 )
79@@ -573,6 +576,31 @@ class DebBinaryUploadFileTests(PackageUploadFileTestCase):
80 [u"RandomData", u"Foo\nbar\nbla\n"],
81 ], bpr.user_defined_fields)
82
83+ def test_built_using(self):
84+ # storeInDatabase parses Built-Using into BinarySourceReference
85+ # rows, and also adds the unparsed contents to user_defined_fields.
86+ uploadfile = self.createDebBinaryUploadFile(
87+ "foo_0.42_i386.deb", "main/python", "unknown", "mypkg", "0.42",
88+ None)
89+ control = self.getBaseControl()
90+ control["Built-Using"] = b"bar (= 0.1)"
91+ uploadfile.parseControl(control)
92+ build = self.factory.makeBinaryPackageBuild()
93+ spph = self.factory.makeSourcePackagePublishingHistory(
94+ archive=build.archive, distroseries=build.distro_series,
95+ pocket=build.pocket, sourcepackagename="bar", version="0.1")
96+ bpr = uploadfile.storeInDatabase(build)
97+ self.assertThat(
98+ bpr.built_using_references,
99+ MatchesSetwise(
100+ MatchesStructure.byEquality(
101+ binary_package_release=bpr,
102+ source_package_release=spph.sourcepackagerelease,
103+ reference_type=BinarySourceReferenceType.BUILT_USING,
104+ )))
105+ self.assertEqual(
106+ [[u"Built-Using", u"bar (= 0.1)"]], bpr.user_defined_fields)
107+
108 def test_homepage(self):
109 # storeInDatabase stores homepage field.
110 uploadfile = self.createDebBinaryUploadFile(
111diff --git a/lib/lp/registry/model/distroseries.py b/lib/lp/registry/model/distroseries.py
112index 51dbeea..fefc8cd 100644
113--- a/lib/lp/registry/model/distroseries.py
114+++ b/lib/lp/registry/model/distroseries.py
115@@ -1,4 +1,4 @@
116-# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
117+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
118 # GNU Affero General Public License version 3 (see the file LICENSE).
119
120 """Database classes for a distribution series."""
121@@ -124,6 +124,7 @@ from lp.services.propertycache import (
122 from lp.services.worlddata.model.language import Language
123 from lp.soyuz.enums import (
124 ArchivePurpose,
125+ BinarySourceReferenceType,
126 IndexCompressionType,
127 PackagePublishingStatus,
128 PackageUploadStatus,
129@@ -1138,6 +1139,9 @@ class DistroSeries(SQLBase, BugTargetBase, HasSpecificationsMixin,
130
131 def getBinaryPackagePublishing(self, archtag, pocket, component, archive):
132 """See `IDistroSeries`."""
133+ # Circular import.
134+ from lp.soyuz.model.binarysourcereference import BinarySourceReference
135+
136 bpphs = Store.of(self).find(
137 BinaryPackagePublishingHistory,
138 DistroArchSeries.distroseries == self,
139@@ -1161,6 +1165,22 @@ class DistroSeries(SQLBase, BugTargetBase, HasSpecificationsMixin,
140 bpbs = load_related(BinaryPackageBuild, bprs, ["buildID"])
141 sprs = load_related(
142 SourcePackageRelease, bpbs, ["source_package_release_id"])
143+
144+ built_using_bsrs = load_referencing(
145+ BinarySourceReference, bprs, ["binary_package_release_id"],
146+ extra_conditions=[
147+ BinarySourceReference.reference_type ==
148+ BinarySourceReferenceType.BUILT_USING,
149+ ])
150+ # Make sure this is initialised for all BPRs, as some may not
151+ # have any BinarySourceReferences.
152+ built_using_bsr_map = {bpr: [] for bpr in bprs}
153+ for bsr in built_using_bsrs:
154+ built_using_bsr_map[bsr.binary_package_release].append(bsr)
155+ for bpr, bsrs in built_using_bsr_map.items():
156+ get_property_cache(bpr).built_using_references = sorted(
157+ bsrs, key=attrgetter("id"))
158+
159 bpfs = load_referencing(
160 BinaryPackageFile, bprs, ["binarypackagereleaseID"])
161 file_map = collections.defaultdict(list)
162diff --git a/lib/lp/soyuz/configure.zcml b/lib/lp/soyuz/configure.zcml
163index 89c9d83..2e97958 100644
164--- a/lib/lp/soyuz/configure.zcml
165+++ b/lib/lp/soyuz/configure.zcml
166@@ -70,6 +70,20 @@
167 set_attributes="changelog"/>
168 </class>
169
170+ <!-- BinarySourceReference -->
171+
172+ <class
173+ class="lp.soyuz.model.binarysourcereference.BinarySourceReference">
174+ <allow
175+ interface="lp.soyuz.interfaces.binarysourcereference.IBinarySourceReference"/>
176+ </class>
177+ <securedutility
178+ class="lp.soyuz.model.binarysourcereference.BinarySourceReferenceSet"
179+ provides="lp.soyuz.interfaces.binarysourcereference.IBinarySourceReferenceSet">
180+ <allow
181+ interface="lp.soyuz.interfaces.binarysourcereference.IBinarySourceReferenceSet"/>
182+ </securedutility>
183+
184 <!-- SourcePackagePublishingHistory -->
185
186 <class
187diff --git a/lib/lp/soyuz/doc/distroarchseriesbinarypackage.txt b/lib/lp/soyuz/doc/distroarchseriesbinarypackage.txt
188index 03d690a..c759bbf 100644
189--- a/lib/lp/soyuz/doc/distroarchseriesbinarypackage.txt
190+++ b/lib/lp/soyuz/doc/distroarchseriesbinarypackage.txt
191@@ -62,6 +62,7 @@ needs to be removed.
192 ... pre_depends=None,
193 ... enhances=None,
194 ... breaks=None,
195+ ... built_using=None,
196 ... essential=False,
197 ... installedsize=0,
198 ... architecturespecific=False,
199@@ -106,6 +107,7 @@ needs to be removed.
200 ... pre_depends=None,
201 ... enhances=None,
202 ... breaks=None,
203+ ... built_using=None,
204 ... essential=False,
205 ... installedsize=0,
206 ... architecturespecific=False,
207diff --git a/lib/lp/soyuz/enums.py b/lib/lp/soyuz/enums.py
208index b8dafa8..641df6b 100644
209--- a/lib/lp/soyuz/enums.py
210+++ b/lib/lp/soyuz/enums.py
211@@ -1,4 +1,4 @@
212-# Copyright 2010-2019 Canonical Ltd. This software is licensed under the
213+# Copyright 2010-2020 Canonical Ltd. This software is licensed under the
214 # GNU Affero General Public License version 3 (see the file LICENSE).
215
216 """Enumerations used in the lp/soyuz modules."""
217@@ -13,6 +13,7 @@ __all__ = [
218 'archive_suffixes',
219 'BinaryPackageFileType',
220 'BinaryPackageFormat',
221+ 'BinarySourceReferenceType',
222 'DistroArchSeriesFilterSense',
223 'IndexCompressionType',
224 'PackageCopyPolicy',
225@@ -613,3 +614,15 @@ class DistroArchSeriesFilterSense(DBEnumeratedType):
226
227 Packages in this package set are excluded from the distro arch series.
228 """)
229+
230+
231+class BinarySourceReferenceType(DBEnumeratedType):
232+ """The type of a reference from a binary package to a source package."""
233+
234+ BUILT_USING = DBItem(1, """
235+ Built-Using
236+
237+ The referencing binary package incorporates part of the referenced
238+ source package, and so the referenced source package needs to remain
239+ in the archive for as long as the referencing binary package does.
240+ """)
241diff --git a/lib/lp/soyuz/interfaces/binarypackagebuild.py b/lib/lp/soyuz/interfaces/binarypackagebuild.py
242index 8546a85..d811c6c 100644
243--- a/lib/lp/soyuz/interfaces/binarypackagebuild.py
244+++ b/lib/lp/soyuz/interfaces/binarypackagebuild.py
245@@ -1,4 +1,4 @@
246-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
247+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
248 # GNU Affero General Public License version 3 (see the file LICENSE).
249
250 """BinaryPackageBuild interfaces."""
251@@ -179,8 +179,8 @@ class IBinaryPackageBuildView(IPackageBuild):
252 component, section, priority, installedsize, architecturespecific,
253 shlibdeps=None, depends=None, recommends=None, suggests=None,
254 conflicts=None, replaces=None, provides=None, pre_depends=None,
255- enhances=None, breaks=None, essential=False, debug_package=None,
256- user_defined_fields=None, homepage=None):
257+ enhances=None, breaks=None, built_using=None, essential=False,
258+ debug_package=None, user_defined_fields=None, homepage=None):
259 """Create and return a `BinaryPackageRelease`.
260
261 The binarypackagerelease will be attached to this specific build.
262diff --git a/lib/lp/soyuz/interfaces/binarypackagerelease.py b/lib/lp/soyuz/interfaces/binarypackagerelease.py
263index 32fe93c..c69a23d 100644
264--- a/lib/lp/soyuz/interfaces/binarypackagerelease.py
265+++ b/lib/lp/soyuz/interfaces/binarypackagerelease.py
266@@ -1,4 +1,4 @@
267-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
268+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
269 # GNU Affero General Public License version 3 (see the file LICENSE).
270
271 """Binary package release interfaces."""
272@@ -61,6 +61,11 @@ class IBinaryPackageRelease(Interface):
273 pre_depends = TextLine(required=False)
274 enhances = TextLine(required=False)
275 breaks = TextLine(required=False)
276+ built_using_references = List(
277+ title=_("Sequence of Built-Using references."),
278+ # Really IBinarySourceReference.
279+ value_type=Reference(schema=Interface),
280+ required=True)
281 essential = Bool(required=False)
282 installedsize = Int(required=False)
283 architecturespecific = Bool(required=True)
284diff --git a/lib/lp/soyuz/interfaces/binarysourcereference.py b/lib/lp/soyuz/interfaces/binarysourcereference.py
285new file mode 100644
286index 0000000..dc21765
287--- /dev/null
288+++ b/lib/lp/soyuz/interfaces/binarysourcereference.py
289@@ -0,0 +1,86 @@
290+# Copyright 2020 Canonical Ltd. This software is licensed under the
291+# GNU Affero General Public License version 3 (see the file LICENSE).
292+
293+"""Interface for references from binary packages to source packages."""
294+
295+from __future__ import absolute_import, print_function, unicode_literals
296+
297+__metaclass__ = type
298+__all__ = [
299+ 'IBinarySourceReference',
300+ 'IBinarySourceReferenceSet',
301+ 'UnparsableBuiltUsing',
302+ ]
303+
304+from lazr.restful.fields import Reference
305+from zope.interface import Interface
306+from zope.schema import (
307+ Choice,
308+ Int,
309+ )
310+
311+from lp import _
312+from lp.soyuz.enums import BinarySourceReferenceType
313+from lp.soyuz.interfaces.binarypackagerelease import IBinaryPackageRelease
314+from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
315+
316+
317+class UnparsableBuiltUsing(Exception):
318+ """A Built-Using field could not be parsed."""
319+
320+
321+class IBinarySourceReference(Interface):
322+ """A reference from a binary package to a source package."""
323+
324+ id = Int(title=_("ID"))
325+
326+ binary_package_release = Reference(
327+ IBinaryPackageRelease,
328+ title=_("The referencing binary package release."),
329+ required=True, readonly=True)
330+ source_package_release = Reference(
331+ ISourcePackageRelease,
332+ title=_("The referenced source package release."),
333+ required=True, readonly=True)
334+ reference_type = Choice(
335+ title=_("The type of the reference."),
336+ vocabulary=BinarySourceReferenceType,
337+ required=True, readonly=True)
338+
339+
340+class IBinarySourceReferenceSet(Interface):
341+ """A set of references from binary packages to source packages."""
342+
343+ def createFromRelationship(bpr, relationship, reference_type):
344+ """Create references from a text relationship field.
345+
346+ :param bpr: The `IBinaryPackageRelease` from which new references
347+ should be created.
348+ :param relationship: A text relationship field containing one or
349+ more source package relations in the usual Debian encoding (e.g.
350+ "source1 (= 1.0), source2 (= 2.0)").
351+ :param reference_type: The `BinarySourceReferenceType` of references
352+ to create.
353+ :return: A list of new `IBinarySourceReference`s.
354+ """
355+
356+ def createFromSourcePackageReleases(bpr, sprs, reference_type):
357+ """Create references from a sequence of source package releases.
358+
359+ This is a convenience method for use in tests.
360+
361+ :param bpr: The `IBinaryPackageRelease` from which new references
362+ should be created.
363+ :param sprs: A sequence of `ISourcePackageRelease`s.
364+ :param reference_type: The `BinarySourceReferenceType` of references
365+ to create.
366+ :return: A list of new `IBinarySourceReference`s.
367+ """
368+
369+ def findByBinaryPackageRelease(bpr, reference_type):
370+ """Find references from a given binary package release.
371+
372+ :param bpr: An `IBinaryPackageRelease` to search for.
373+ :param reference_type: A `BinarySourceReferenceType` to search for.
374+ :return: A `ResultSet` of matching `IBinarySourceReference`s.
375+ """
376diff --git a/lib/lp/soyuz/model/binarypackagebuild.py b/lib/lp/soyuz/model/binarypackagebuild.py
377index ae4f1de..470a8f2 100644
378--- a/lib/lp/soyuz/model/binarypackagebuild.py
379+++ b/lib/lp/soyuz/model/binarypackagebuild.py
380@@ -1,4 +1,4 @@
381-# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
382+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
383 # GNU Affero General Public License version 3 (see the file LICENSE).
384
385 __metaclass__ = type
386@@ -91,6 +91,7 @@ from lp.services.macaroons.model import MacaroonIssuerBase
387 from lp.soyuz.adapters.buildarch import determine_architectures_to_build
388 from lp.soyuz.enums import (
389 ArchivePurpose,
390+ BinarySourceReferenceType,
391 PackagePublishingStatus,
392 )
393 from lp.soyuz.interfaces.archive import (
394@@ -104,6 +105,9 @@ from lp.soyuz.interfaces.binarypackagebuild import (
395 IBinaryPackageBuildSet,
396 UnparsableDependencies,
397 )
398+from lp.soyuz.interfaces.binarysourcereference import (
399+ IBinarySourceReferenceSet,
400+ )
401 from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries
402 from lp.soyuz.interfaces.packageset import IPackagesetSet
403 from lp.soyuz.mail.binarypackagebuild import BinaryPackageBuildMailer
404@@ -662,10 +666,11 @@ class BinaryPackageBuild(PackageBuildMixin, SQLBase):
405 binpackageformat, component, section, priority, installedsize,
406 architecturespecific, shlibdeps=None, depends=None, recommends=None,
407 suggests=None, conflicts=None, replaces=None, provides=None,
408- pre_depends=None, enhances=None, breaks=None, essential=False,
409- debug_package=None, user_defined_fields=None, homepage=None):
410+ pre_depends=None, enhances=None, breaks=None, built_using=None,
411+ essential=False, debug_package=None, user_defined_fields=None,
412+ homepage=None):
413 """See IBuild."""
414- return BinaryPackageRelease(
415+ bpr = BinaryPackageRelease(
416 build=self, binarypackagename=binarypackagename, version=version,
417 summary=summary, description=description,
418 binpackageformat=binpackageformat,
419@@ -677,6 +682,10 @@ class BinaryPackageBuild(PackageBuildMixin, SQLBase):
420 architecturespecific=architecturespecific,
421 debug_package=debug_package,
422 user_defined_fields=user_defined_fields, homepage=homepage)
423+ if built_using:
424+ getUtility(IBinarySourceReferenceSet).createFromRelationship(
425+ bpr, built_using, BinarySourceReferenceType.BUILT_USING)
426+ return bpr
427
428 def estimateDuration(self):
429 """See `IPackageBuild`."""
430diff --git a/lib/lp/soyuz/model/binarypackagerelease.py b/lib/lp/soyuz/model/binarypackagerelease.py
431index ad4f514..3458b70 100644
432--- a/lib/lp/soyuz/model/binarypackagerelease.py
433+++ b/lib/lp/soyuz/model/binarypackagerelease.py
434@@ -1,4 +1,4 @@
435-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
436+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
437 # GNU Affero General Public License version 3 (see the file LICENSE).
438
439 __metaclass__ = type
440@@ -7,6 +7,7 @@ __all__ = [
441 'BinaryPackageReleaseDownloadCount',
442 ]
443
444+from operator import attrgetter
445
446 import simplejson
447 from sqlobject import (
448@@ -22,6 +23,7 @@ from storm.locals import (
449 Store,
450 Storm,
451 )
452+from zope.component import getUtility
453 from zope.interface import implementer
454
455 from lp.services.database.constants import UTC_NOW
456@@ -35,12 +37,16 @@ from lp.services.propertycache import (
457 from lp.soyuz.enums import (
458 BinaryPackageFileType,
459 BinaryPackageFormat,
460+ BinarySourceReferenceType,
461 PackagePublishingPriority,
462 )
463 from lp.soyuz.interfaces.binarypackagerelease import (
464 IBinaryPackageRelease,
465 IBinaryPackageReleaseDownloadCount,
466 )
467+from lp.soyuz.interfaces.binarysourcereference import (
468+ IBinarySourceReferenceSet,
469+ )
470 from lp.soyuz.model.files import BinaryPackageFile
471
472
473@@ -89,6 +95,14 @@ class BinaryPackageRelease(SQLBase):
474 del kwargs['user_defined_fields']
475 super(BinaryPackageRelease, self).__init__(*args, **kwargs)
476
477+ @cachedproperty
478+ def built_using_references(self):
479+ reference_set = getUtility(IBinarySourceReferenceSet)
480+ references = reference_set.findByBinaryPackageRelease(
481+ self, BinarySourceReferenceType.BUILT_USING)
482+ # Preserving insertion order is good enough.
483+ return sorted(references, key=attrgetter('id'))
484+
485 @property
486 def user_defined_fields(self):
487 """See `IBinaryPackageRelease`."""
488diff --git a/lib/lp/soyuz/model/binarysourcereference.py b/lib/lp/soyuz/model/binarysourcereference.py
489new file mode 100644
490index 0000000..adcfb6f
491--- /dev/null
492+++ b/lib/lp/soyuz/model/binarysourcereference.py
493@@ -0,0 +1,149 @@
494+# Copyright 2020 Canonical Ltd. This software is licensed under the
495+# GNU Affero General Public License version 3 (see the file LICENSE).
496+
497+"""References from binary packages to source packages."""
498+
499+from __future__ import absolute_import, print_function, unicode_literals
500+
501+__metaclass__ = type
502+__all__ = [
503+ 'BinarySourceReference',
504+ 'BinarySourceReferenceSet',
505+ ]
506+
507+import warnings
508+
509+from debian.deb822 import PkgRelation
510+from storm.locals import (
511+ Int,
512+ Reference,
513+ )
514+from zope.interface import implementer
515+
516+from lp.services.database.bulk import create
517+from lp.services.database.enumcol import DBEnum
518+from lp.services.database.interfaces import IStore
519+from lp.services.database.stormbase import StormBase
520+from lp.soyuz.adapters.archivedependencies import pocket_dependencies
521+from lp.soyuz.enums import BinarySourceReferenceType
522+from lp.soyuz.interfaces.binarysourcereference import (
523+ IBinarySourceReference,
524+ IBinarySourceReferenceSet,
525+ UnparsableBuiltUsing,
526+ )
527+
528+
529+@implementer(IBinarySourceReference)
530+class BinarySourceReference(StormBase):
531+ """See `IBinarySourceReference`."""
532+
533+ __storm_table__ = "BinarySourceReference"
534+
535+ id = Int(primary=True)
536+
537+ binary_package_release_id = Int(
538+ name="binary_package_release", allow_none=False)
539+ binary_package_release = Reference(
540+ binary_package_release_id, "BinaryPackageRelease.id")
541+
542+ source_package_release_id = Int(
543+ name="source_package_release", allow_none=False)
544+ source_package_release = Reference(
545+ source_package_release_id, "SourcePackageRelease.id")
546+
547+ reference_type = DBEnum(enum=BinarySourceReferenceType, allow_none=False)
548+
549+ def __init__(self, binary_package_release, source_package_release,
550+ reference_type):
551+ """Construct a `BinarySourceReference`."""
552+ super(BinarySourceReference, self).__init__()
553+ self.binary_package_release = binary_package_release
554+ self.source_package_release = source_package_release
555+ self.reference_type = reference_type
556+
557+
558+@implementer(IBinarySourceReferenceSet)
559+class BinarySourceReferenceSet:
560+ """See `IBinarySourceReferenceSet`."""
561+
562+ @classmethod
563+ def createFromRelationship(cls, bpr, relationship, reference_type):
564+ """See `IBinarySourceReferenceSet`."""
565+ if not relationship:
566+ return []
567+
568+ try:
569+ with warnings.catch_warnings():
570+ warnings.simplefilter("error")
571+ parsed_rel = PkgRelation.parse_relations(relationship)
572+ except Warning as error:
573+ raise UnparsableBuiltUsing(
574+ "Invalid Built-Using field; cannot be parsed by deb822: %s"
575+ % (error,))
576+
577+ build = bpr.build
578+ values = []
579+ for or_rel in parsed_rel:
580+ if len(or_rel) != 1:
581+ raise UnparsableBuiltUsing(
582+ "Alternatives are not allowed in Built-Using field: %s"
583+ % (PkgRelation.str([or_rel]),))
584+ rel = or_rel[0]
585+ if rel["version"] is None or rel["version"][0] != "=":
586+ raise UnparsableBuiltUsing(
587+ "Built-Using must contain strict dependencies: %s"
588+ % (PkgRelation.str([or_rel]),))
589+
590+ # "source-package-name (= version)" might refer to any of
591+ # several SPRs, for example if the same source package was
592+ # uploaded to a PPA and then uploaded separately (not copied -
593+ # copies reuse the same SPR) to the distribution's primary
594+ # archive. We need to disambiguate this and find an actual SPR
595+ # so that we can efficiently look up references for a given
596+ # source publication.
597+ #
598+ # However, allowing cross-archive references would make the
599+ # dominator's job much harder and have other undesirable
600+ # properties, such as being able to pin a source in Published in
601+ # a foreign archive just by adding it as a dependency and
602+ # declaring a Built-Using relationship on it.
603+ #
604+ # Therefore, as a safe approximation, try this build's pocket
605+ # dependencies within its archive and series. Within this
606+ # constraint, a name and version should uniquely identify an
607+ # SPR, although we pick the latest by ID just in case that
608+ # somehow ends up not being true.
609+ closest_spph = build.archive.getPublishedSources(
610+ name=rel["name"], version=rel["version"][1],
611+ distroseries=build.distro_series,
612+ pocket=pocket_dependencies[build.pocket],
613+ exact_match=True).first()
614+ if closest_spph is None:
615+ raise UnparsableBuiltUsing(
616+ "Built-Using refers to source package %s (= %s), which is "
617+ "not known in %s in %s" %
618+ (rel["name"], rel["version"][1],
619+ build.distro_series.name, build.archive.reference))
620+ values.append(
621+ (bpr.id, closest_spph.sourcepackagereleaseID, reference_type))
622+
623+ return create(
624+ (BinarySourceReference.binary_package_release_id,
625+ BinarySourceReference.source_package_release_id,
626+ BinarySourceReference.reference_type),
627+ values, get_objects=True)
628+
629+ @classmethod
630+ def createFromSourcePackageReleases(cls, bpr, sprs, reference_type):
631+ """See `IBinarySourceReferenceSet`."""
632+ relationship = ", ".join(
633+ ["%s (= %s)" % (spr.name, spr.version) for spr in sprs])
634+ return cls.createFromRelationship(bpr, relationship, reference_type)
635+
636+ @classmethod
637+ def findByBinaryPackageRelease(cls, bpr, reference_type):
638+ """See `IBinarySourceReferenceSet`."""
639+ return IStore(BinarySourceReference).find(
640+ BinarySourceReference,
641+ BinarySourceReference.binary_package_release == bpr,
642+ BinarySourceReference.reference_type == reference_type)
643diff --git a/lib/lp/soyuz/scripts/gina/handlers.py b/lib/lp/soyuz/scripts/gina/handlers.py
644index 66d2aeb..54539a4 100644
645--- a/lib/lp/soyuz/scripts/gina/handlers.py
646+++ b/lib/lp/soyuz/scripts/gina/handlers.py
647@@ -1,4 +1,4 @@
648-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
649+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
650 # GNU Affero General Public License version 3 (see the file LICENSE).
651
652 """Gina db handlers.
653@@ -52,10 +52,15 @@ from lp.services.librarian.interfaces import ILibraryFileAliasSet
654 from lp.services.scripts import log
655 from lp.soyuz.enums import (
656 BinaryPackageFormat,
657+ BinarySourceReferenceType,
658 PackagePublishingStatus,
659 )
660 from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet
661 from lp.soyuz.interfaces.binarypackagename import IBinaryPackageNameSet
662+from lp.soyuz.interfaces.binarysourcereference import (
663+ IBinarySourceReferenceSet,
664+ UnparsableBuiltUsing,
665+ )
666 from lp.soyuz.interfaces.publishing import (
667 active_publishing_status,
668 IPublishingSet,
669@@ -820,6 +825,16 @@ class BinaryPackageHandler:
670 installedsize=bin.installed_size,
671 architecturespecific=architecturespecific,
672 **kwargs)
673+ try:
674+ getUtility(IBinarySourceReferenceSet).createFromRelationship(
675+ binpkg, bin.built_using, BinarySourceReferenceType.BUILT_USING)
676+ except UnparsableBuiltUsing:
677+ # XXX cjwatson 2020-02-03: It might be nice if we created
678+ # BinarySourceReference rows at least for those relations that
679+ # can be parsed and resolved to SourcePackageReleases. It's not
680+ # worth spending much time on given that we don't use binary
681+ # imports much, though.
682+ pass
683 log.info('Binary Package Release %s (%s) created' %
684 (bin_name.name, bin.version))
685
686diff --git a/lib/lp/soyuz/scripts/gina/packages.py b/lib/lp/soyuz/scripts/gina/packages.py
687index 4126b80..6e2f090 100644
688--- a/lib/lp/soyuz/scripts/gina/packages.py
689+++ b/lib/lp/soyuz/scripts/gina/packages.py
690@@ -1,4 +1,4 @@
691-# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
692+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
693 # GNU Affero General Public License version 3 (see the file LICENSE).
694
695 """Package information classes.
696@@ -466,6 +466,7 @@ class BinaryPackageData(AbstractPackageData):
697 pre_depends = ""
698 enhances = ""
699 breaks = ""
700+ built_using = ""
701 essential = False
702
703 # Overwritten in do_package, optionally
704@@ -493,6 +494,14 @@ class BinaryPackageData(AbstractPackageData):
705 except ValueError:
706 raise MissingRequiredArguments("Installed-Size is "
707 "not a valid integer: %r" % v)
708+ elif k == "Built-Using":
709+ self.built_using = v
710+ # Preserve the original form of Built-Using to avoid
711+ # possible unfortunate apt behaviour. This is most easily
712+ # done by adding it to _user_defined as well.
713+ if self._user_defined is None:
714+ self._user_defined = []
715+ self._user_defined.append([k, v])
716 else:
717 self.set_field(k, v)
718
719diff --git a/lib/lp/soyuz/scripts/tests/test_gina.py b/lib/lp/soyuz/scripts/tests/test_gina.py
720index 2653b4f..9574c38 100644
721--- a/lib/lp/soyuz/scripts/tests/test_gina.py
722+++ b/lib/lp/soyuz/scripts/tests/test_gina.py
723@@ -1,4 +1,4 @@
724-# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
725+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
726 # GNU Affero General Public License version 3 (see the file LICENSE).
727
728 from doctest import DocTestSuite
729@@ -12,6 +12,10 @@ from unittest import TestLoader
730
731 import apt_pkg
732 from fixtures import EnvironmentVariableFixture
733+from testtools.matchers import (
734+ MatchesSetwise,
735+ MatchesStructure,
736+ )
737 import transaction
738
739 from lp.archiveuploader.tagfiles import parse_tagfile
740@@ -22,7 +26,10 @@ from lp.services.features.testing import FeatureFixture
741 from lp.services.log.logger import DevNullLogger
742 from lp.services.osutils import write_file
743 from lp.services.tarfile_helpers import LaunchpadWriteTarFile
744-from lp.soyuz.enums import PackagePublishingStatus
745+from lp.soyuz.enums import (
746+ BinarySourceReferenceType,
747+ PackagePublishingStatus,
748+ )
749 from lp.soyuz.scripts.gina import ExecutionError
750 from lp.soyuz.scripts.gina.archive import (
751 ArchiveComponentItems,
752@@ -483,9 +490,62 @@ class TestBinaryPackageHandler(TestCaseWithFactory):
753 "Summary": "",
754 "Priority": "extra",
755 "Python-Version": "2.7",
756+ "Built-Using": "nonexistent (= 0.1)",
757+ }
758+ bp_data = BinaryPackageData(**deb_contents)
759+ self.assertContentEqual(
760+ [["Python-Version", "2.7"],
761+ ["Built-Using", "nonexistent (= 0.1)"]],
762+ bp_data._user_defined)
763+ bp_data.archive_root = archive_root
764+ # We don't need a real .deb here.
765+ write_file(
766+ os.path.join(archive_root, "pool/main/f/foo/foo_1.0-1_amd64.deb"),
767+ b"x")
768+ bpr = bphandler.createBinaryPackage(bp_data, spr, das, "amd64")
769+ self.assertIsNotNone(bpr)
770+ self.assertEqual([], bpr.built_using_references)
771+ self.assertContentEqual(
772+ [["Python-Version", "2.7"],
773+ ["Built-Using", "nonexistent (= 0.1)"]],
774+ bpr.user_defined_fields)
775+
776+ def test_resolvable_built_using(self):
777+ das = self.factory.makeDistroArchSeries()
778+ archive_root = self.useTempDir()
779+ sphandler = SourcePackageHandler(
780+ das.distroseries.distribution.name, archive_root,
781+ PackagePublishingPocket.RELEASE, None)
782+ bphandler = BinaryPackageHandler(
783+ sphandler, archive_root, PackagePublishingPocket.RELEASE)
784+ spr = self.factory.makeSourcePackagePublishingHistory(
785+ distroseries=das.distroseries,
786+ component="main").sourcepackagerelease
787+ built_using_spph = self.factory.makeSourcePackagePublishingHistory(
788+ archive=das.main_archive, distroseries=das.distroseries,
789+ pocket=PackagePublishingPocket.RELEASE)
790+ built_using_spr = built_using_spph.sourcepackagerelease
791+ built_using_relationship = "%s (= %s)" % (
792+ built_using_spr.name, built_using_spr.version)
793+ deb_contents = {
794+ "Package": "foo",
795+ "Installed-Size": "0",
796+ "Maintainer": "Foo Bar <foo@canonical.com>",
797+ "Section": "misc",
798+ "Architecture": "amd64",
799+ "Version": "1.0-1",
800+ "Filename": "pool/main/f/foo/foo_1.0-1_amd64.deb",
801+ "Component": "main",
802+ "Size": "0",
803+ "MD5sum": "0" * 32,
804+ "Description": "",
805+ "Summary": "",
806+ "Priority": "extra",
807+ "Built-Using": built_using_relationship,
808 }
809 bp_data = BinaryPackageData(**deb_contents)
810- self.assertEqual([["Python-Version", "2.7"]], bp_data._user_defined)
811+ self.assertContentEqual(
812+ [["Built-Using", built_using_relationship]], bp_data._user_defined)
813 bp_data.archive_root = archive_root
814 # We don't need a real .deb here.
815 write_file(
816@@ -493,7 +553,16 @@ class TestBinaryPackageHandler(TestCaseWithFactory):
817 b"x")
818 bpr = bphandler.createBinaryPackage(bp_data, spr, das, "amd64")
819 self.assertIsNotNone(bpr)
820- self.assertEqual([["Python-Version", "2.7"]], bpr.user_defined_fields)
821+ self.assertThat(
822+ bpr.built_using_references,
823+ MatchesSetwise(
824+ MatchesStructure.byEquality(
825+ binary_package_release=bpr,
826+ source_package_release=built_using_spr,
827+ reference_type=BinarySourceReferenceType.BUILT_USING)))
828+ self.assertContentEqual(
829+ [["Built-Using", built_using_relationship]],
830+ bpr.user_defined_fields)
831
832
833 class TestBinaryPackagePublisher(TestCaseWithFactory):
834diff --git a/lib/lp/soyuz/tests/test_binarysourcereference.py b/lib/lp/soyuz/tests/test_binarysourcereference.py
835new file mode 100644
836index 0000000..f682de8
837--- /dev/null
838+++ b/lib/lp/soyuz/tests/test_binarysourcereference.py
839@@ -0,0 +1,208 @@
840+# Copyright 2020 Canonical Ltd. This software is licensed under the
841+# GNU Affero General Public License version 3 (see the file LICENSE).
842+
843+"""Test references from binary packages to source packages."""
844+
845+from __future__ import absolute_import, print_function, unicode_literals
846+
847+__metaclass__ = type
848+
849+import re
850+
851+from testtools.matchers import (
852+ MatchesSetwise,
853+ MatchesStructure,
854+ )
855+from testtools.testcase import ExpectedException
856+from zope.component import getUtility
857+
858+from lp.registry.interfaces.pocket import PackagePublishingPocket
859+from lp.soyuz.enums import (
860+ ArchivePurpose,
861+ BinarySourceReferenceType,
862+ )
863+from lp.soyuz.interfaces.binarysourcereference import (
864+ IBinarySourceReferenceSet,
865+ UnparsableBuiltUsing,
866+ )
867+from lp.testing import TestCaseWithFactory
868+from lp.testing.layers import DatabaseFunctionalLayer
869+
870+
871+class TestBinarySourceReference(TestCaseWithFactory):
872+
873+ layer = DatabaseFunctionalLayer
874+
875+ def setUp(self):
876+ super(TestBinarySourceReference, self).setUp()
877+ self.reference_set = getUtility(IBinarySourceReferenceSet)
878+
879+ def test_createFromRelationship_empty(self):
880+ bpr = self.factory.makeBinaryPackageRelease()
881+ self.assertEqual(
882+ [],
883+ self.reference_set.createFromRelationship(
884+ bpr, "", BinarySourceReferenceType.BUILT_USING))
885+
886+ def test_createFromRelationship_nonsense(self):
887+ bpr = self.factory.makeBinaryPackageRelease()
888+ expected_message = (
889+ r"Invalid Built-Using field; cannot be parsed by deb822: .*")
890+ with ExpectedException(UnparsableBuiltUsing, expected_message):
891+ self.reference_set.createFromRelationship(
892+ bpr, "nonsense (", BinarySourceReferenceType.BUILT_USING)
893+
894+ def test_createFromRelationship_alternatives(self):
895+ bpr = self.factory.makeBinaryPackageRelease()
896+ expected_message = (
897+ r"Alternatives are not allowed in Built-Using field: "
898+ r"foo \(= 1\) \| bar \(= 2\)")
899+ with ExpectedException(UnparsableBuiltUsing, expected_message):
900+ self.reference_set.createFromRelationship(
901+ bpr, "foo (= 1) | bar (= 2)",
902+ BinarySourceReferenceType.BUILT_USING)
903+
904+ def test_createFromRelationship_no_version(self):
905+ bpr = self.factory.makeBinaryPackageRelease()
906+ expected_message = r"Built-Using must contain strict dependencies: foo"
907+ with ExpectedException(UnparsableBuiltUsing, expected_message):
908+ self.reference_set.createFromRelationship(
909+ bpr, "foo", BinarySourceReferenceType.BUILT_USING)
910+
911+ def test_createFromRelationship_inequality(self):
912+ bpr = self.factory.makeBinaryPackageRelease()
913+ expected_message = (
914+ r"Built-Using must contain strict dependencies: foo \(>= 1\)")
915+ with ExpectedException(UnparsableBuiltUsing, expected_message):
916+ self.reference_set.createFromRelationship(
917+ bpr, "foo (>= 1)", BinarySourceReferenceType.BUILT_USING)
918+
919+ def test_createFromRelationship_unknown_source_package_name(self):
920+ bpr = self.factory.makeBinaryPackageRelease()
921+ relationship = "nonexistent (= 1)"
922+ expected_message = (
923+ r"Built-Using refers to source package %s, which is not known in "
924+ r"%s in %s" % (
925+ re.escape(relationship), bpr.build.distro_series.name,
926+ bpr.build.archive.reference))
927+ with ExpectedException(UnparsableBuiltUsing, expected_message):
928+ self.reference_set.createFromRelationship(
929+ bpr, relationship, BinarySourceReferenceType.BUILT_USING)
930+
931+ def test_createFromRelationship_unknown_source_package_version(self):
932+ bpr = self.factory.makeBinaryPackageRelease()
933+ spph = self.factory.makeSourcePackagePublishingHistory(
934+ archive=bpr.build.archive,
935+ distroseries=bpr.build.distro_series,
936+ component=bpr.build.current_component)
937+ spr = spph.sourcepackagerelease
938+ relationship = "%s (= %s.1)" % (spr.name, spr.version)
939+ expected_message = (
940+ r"Built-Using refers to source package %s, which is not known in "
941+ r"%s in %s" % (
942+ re.escape(relationship), bpr.build.distro_series.name,
943+ bpr.build.archive.reference))
944+ with ExpectedException(UnparsableBuiltUsing, expected_message):
945+ self.reference_set.createFromRelationship(
946+ bpr, relationship, BinarySourceReferenceType.BUILT_USING)
947+
948+ def test_createFromRelationship_simple(self):
949+ bpr = self.factory.makeBinaryPackageRelease()
950+ spphs = [
951+ self.factory.makeSourcePackagePublishingHistory(
952+ archive=bpr.build.archive,
953+ distroseries=bpr.build.distro_series, pocket=bpr.build.pocket)
954+ for _ in range(3)]
955+ sprs = [spph.sourcepackagerelease for spph in spphs]
956+ # Create a few more SPPHs with slight mismatches to ensure that
957+ # createFromRelationship matches correctly.
958+ self.factory.makeSourcePackagePublishingHistory(
959+ archive=bpr.build.archive, pocket=bpr.build.pocket,
960+ sourcepackagename=sprs[0].name, version=sprs[0].version)
961+ self.factory.makeSourcePackagePublishingHistory(
962+ archive=bpr.build.archive, distroseries=bpr.build.distro_series,
963+ pocket=PackagePublishingPocket.BACKPORTS,
964+ sourcepackagename=sprs[0].name, version=sprs[0].version)
965+ self.factory.makeSourcePackagePublishingHistory(
966+ archive=bpr.build.archive, distroseries=bpr.build.distro_series,
967+ pocket=bpr.build.pocket, sourcepackagename=sprs[0].name)
968+ self.factory.makeSourcePackagePublishingHistory(
969+ archive=bpr.build.archive, distroseries=bpr.build.distro_series,
970+ pocket=bpr.build.pocket, version=sprs[0].version)
971+ self.factory.makeSourcePackagePublishingHistory()
972+ relationship = (
973+ "%s (= %s), %s (= %s)" %
974+ (sprs[0].name, sprs[0].version, sprs[1].name, sprs[1].version))
975+ bsrs = self.reference_set.createFromRelationship(
976+ bpr, relationship, BinarySourceReferenceType.BUILT_USING)
977+ self.assertThat(bsrs, MatchesSetwise(*(
978+ MatchesStructure.byEquality(
979+ binary_package_release=bpr,
980+ source_package_release=spr,
981+ reference_type=BinarySourceReferenceType.BUILT_USING)
982+ for spr in sprs[:2])))
983+
984+ def test_createFromRelationship_foreign_archive(self):
985+ # createFromRelationship only considers SPRs found in the same
986+ # archive as the build.
987+ archive = self.factory.makeArchive(purpose=ArchivePurpose.PPA)
988+ build = self.factory.makeBinaryPackageBuild(archive=archive)
989+ bpr = self.factory.makeBinaryPackageRelease(build=build)
990+ spph = self.factory.makeSourcePackagePublishingHistory(
991+ archive=build.distro_series.main_archive,
992+ distroseries=build.distro_series, pocket=build.pocket)
993+ spr = spph.sourcepackagerelease
994+ relationship = "%s (= %s)" % (spr.name, spr.version)
995+ expected_message = (
996+ r"Built-Using refers to source package %s, which is not known in "
997+ r"%s in %s" % (
998+ re.escape(relationship), build.distro_series.name,
999+ build.archive.reference))
1000+ with ExpectedException(UnparsableBuiltUsing, expected_message):
1001+ self.reference_set.createFromRelationship(
1002+ bpr, relationship, BinarySourceReferenceType.BUILT_USING)
1003+
1004+ def test_findByBinaryPackageRelease_empty(self):
1005+ bpr = self.factory.makeBinaryPackageRelease()
1006+ self.assertContentEqual(
1007+ [],
1008+ self.reference_set.findByBinaryPackageRelease(
1009+ bpr, BinarySourceReferenceType.BUILT_USING))
1010+
1011+ def test_findByBinaryPackageRelease(self):
1012+ bprs = [self.factory.makeBinaryPackageRelease() for _ in range(2)]
1013+ all_sprs = []
1014+ for bpr in bprs:
1015+ spphs = [
1016+ self.factory.makeSourcePackagePublishingHistory(
1017+ archive=bpr.build.archive,
1018+ distroseries=bpr.build.distro_series,
1019+ pocket=bpr.build.pocket)
1020+ for _ in range(2)]
1021+ sprs = [spph.sourcepackagerelease for spph in spphs]
1022+ all_sprs.extend(sprs)
1023+ self.reference_set.createFromSourcePackageReleases(
1024+ bpr, sprs, BinarySourceReferenceType.BUILT_USING)
1025+ other_bpr = self.factory.makeBinaryPackageRelease()
1026+ self.assertThat(
1027+ self.reference_set.findByBinaryPackageRelease(
1028+ bprs[0], BinarySourceReferenceType.BUILT_USING),
1029+ MatchesSetwise(*(
1030+ MatchesStructure.byEquality(
1031+ binary_package_release=bprs[0],
1032+ source_package_release=spr,
1033+ reference_type=BinarySourceReferenceType.BUILT_USING)
1034+ for spr in all_sprs[:2])))
1035+ self.assertThat(
1036+ self.reference_set.findByBinaryPackageRelease(
1037+ bprs[1], BinarySourceReferenceType.BUILT_USING),
1038+ MatchesSetwise(*(
1039+ MatchesStructure.byEquality(
1040+ binary_package_release=bprs[1],
1041+ source_package_release=spr,
1042+ reference_type=BinarySourceReferenceType.BUILT_USING)
1043+ for spr in all_sprs[2:])))
1044+ self.assertContentEqual(
1045+ [],
1046+ self.reference_set.findByBinaryPackageRelease(
1047+ other_bpr, BinarySourceReferenceType.BUILT_USING))
1048diff --git a/lib/lp/soyuz/tests/test_publishing.py b/lib/lp/soyuz/tests/test_publishing.py
1049index e65003d..cd7d1ea 100644
1050--- a/lib/lp/soyuz/tests/test_publishing.py
1051+++ b/lib/lp/soyuz/tests/test_publishing.py
1052@@ -306,7 +306,8 @@ class SoyuzTestPublisher:
1053 shlibdep=None, depends=None, recommends=None,
1054 suggests=None, conflicts=None, replaces=None,
1055 provides=None, pre_depends=None, enhances=None,
1056- breaks=None, filecontent=b'bbbiiinnnaaarrryyy',
1057+ breaks=None, built_using=None,
1058+ filecontent=b'bbbiiinnnaaarrryyy',
1059 changes_file_content=b"Fake: fake changes file",
1060 status=PackagePublishingStatus.PENDING,
1061 pocket=PackagePublishingPocket.RELEASE,
1062@@ -353,7 +354,8 @@ class SoyuzTestPublisher:
1063 build, binaryname + '-dbgsym', filecontent, summary,
1064 description, shlibdep, depends, recommends, suggests,
1065 conflicts, replaces, provides, pre_depends, enhances,
1066- breaks, BinaryPackageFormat.DDEB, version=version)
1067+ breaks, built_using, BinaryPackageFormat.DDEB,
1068+ version=version)
1069 pub_binaries += self.publishBinaryInArchive(
1070 binarypackagerelease_ddeb, archive, status,
1071 pocket, scheduleddeletiondate, dateremoved,
1072@@ -364,7 +366,7 @@ class SoyuzTestPublisher:
1073 binarypackagerelease = self.uploadBinaryForBuild(
1074 build, binaryname, filecontent, summary, description,
1075 shlibdep, depends, recommends, suggests, conflicts, replaces,
1076- provides, pre_depends, enhances, breaks, format,
1077+ provides, pre_depends, enhances, breaks, built_using, format,
1078 binarypackagerelease_ddeb, version=version,
1079 user_defined_fields=user_defined_fields)
1080 pub_binaries += self.publishBinaryInArchive(
1081@@ -387,8 +389,9 @@ class SoyuzTestPublisher:
1082 summary="summary", description="description", shlibdep=None,
1083 depends=None, recommends=None, suggests=None, conflicts=None,
1084 replaces=None, provides=None, pre_depends=None, enhances=None,
1085- breaks=None, format=BinaryPackageFormat.DEB, debug_package=None,
1086- user_defined_fields=None, homepage=None, version=None):
1087+ breaks=None, built_using=None, format=BinaryPackageFormat.DEB,
1088+ debug_package=None, user_defined_fields=None, homepage=None,
1089+ version=None):
1090 """Return the corresponding `BinaryPackageRelease`."""
1091 sourcepackagerelease = build.source_package_release
1092 distroarchseries = build.distro_arch_series
1093@@ -418,6 +421,7 @@ class SoyuzTestPublisher:
1094 pre_depends=pre_depends,
1095 enhances=enhances,
1096 breaks=breaks,
1097+ built_using=built_using,
1098 essential=False,
1099 installedsize=100,
1100 architecturespecific=architecturespecific,

Subscribers

People subscribed via source and target branches

to status/vote changes: