Merge ~cjwatson/launchpad:dominator-channel-map into launchpad:master
- Git
- lp:~cjwatson/launchpad
- dominator-channel-map
- Merge into master
Proposed by
Colin Watson
Status: | Merged |
---|---|
Approved by: | Colin Watson |
Approved revision: | 038e321722bbc735367ae64ce92dac32aa528f80 |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~cjwatson/launchpad:dominator-channel-map |
Merge into: | launchpad:master |
Diff against target: |
1347 lines (+456/-124) 14 files modified
lib/lp/archivepublisher/domination.py (+73/-26) lib/lp/archivepublisher/publishing.py (+3/-0) lib/lp/archivepublisher/tests/test_dominator.py (+74/-9) lib/lp/code/interfaces/cibuild.py (+8/-0) lib/lp/code/model/cibuild.py (+13/-0) lib/lp/code/model/tests/test_cibuild.py (+19/-0) lib/lp/soyuz/adapters/packagelocation.py (+27/-5) lib/lp/soyuz/adapters/tests/test_packagelocation.py (+20/-2) lib/lp/soyuz/enums.py (+14/-0) lib/lp/soyuz/interfaces/publishing.py (+6/-8) lib/lp/soyuz/model/binarypackagerelease.py (+2/-0) lib/lp/soyuz/model/publishing.py (+78/-33) lib/lp/soyuz/tests/test_publishing.py (+70/-20) lib/lp/testing/factory.py (+49/-21) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+422233@code.launchpad.net |
Commit message
Support dominating publications by channel
Description of the change
This means that, once we start publishing binaries to snap-store-style semantic channels (e.g. "stable", "1.0/candidate", etc.), the dominator will be able to correctly supersede publications that have been replaced with a different version of the same package in the same channel.
I had to prepare for this by adding channel support to various publishing primitives. There's no UI or API support for any of this yet, but I at least needed enough to support the dominator and its tests.
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
review:
Approve
(code)
Revision history for this message
Colin Watson (cjwatson) : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/archivepublisher/domination.py b/lib/lp/archivepublisher/domination.py | |||
2 | index 5cc9422..526c8ea 100644 | |||
3 | --- a/lib/lp/archivepublisher/domination.py | |||
4 | +++ b/lib/lp/archivepublisher/domination.py | |||
5 | @@ -64,6 +64,7 @@ from storm.expr import ( | |||
6 | 64 | And, | 64 | And, |
7 | 65 | Count, | 65 | Count, |
8 | 66 | Desc, | 66 | Desc, |
9 | 67 | Not, | ||
10 | 67 | Select, | 68 | Select, |
11 | 68 | ) | 69 | ) |
12 | 69 | from zope.component import getUtility | 70 | from zope.component import getUtility |
13 | @@ -74,7 +75,9 @@ from lp.services.database.constants import UTC_NOW | |||
14 | 74 | from lp.services.database.decoratedresultset import DecoratedResultSet | 75 | from lp.services.database.decoratedresultset import DecoratedResultSet |
15 | 75 | from lp.services.database.interfaces import IStore | 76 | from lp.services.database.interfaces import IStore |
16 | 76 | from lp.services.database.sqlbase import flush_database_updates | 77 | from lp.services.database.sqlbase import flush_database_updates |
17 | 78 | from lp.services.database.stormexpr import IsDistinctFrom | ||
18 | 77 | from lp.services.orderingcheck import OrderingCheck | 79 | from lp.services.orderingcheck import OrderingCheck |
19 | 80 | from lp.soyuz.adapters.packagelocation import PackageLocation | ||
20 | 78 | from lp.soyuz.enums import ( | 81 | from lp.soyuz.enums import ( |
21 | 79 | BinaryPackageFormat, | 82 | BinaryPackageFormat, |
22 | 80 | PackagePublishingStatus, | 83 | PackagePublishingStatus, |
23 | @@ -204,6 +207,17 @@ class GeneralizedPublication: | |||
24 | 204 | return sorted(publications, key=cmp_to_key(self.compare), reverse=True) | 207 | return sorted(publications, key=cmp_to_key(self.compare), reverse=True) |
25 | 205 | 208 | ||
26 | 206 | 209 | ||
27 | 210 | def make_package_location(pub): | ||
28 | 211 | """Make a `PackageLocation` representing a publication.""" | ||
29 | 212 | return PackageLocation( | ||
30 | 213 | archive=pub.archive, | ||
31 | 214 | distribution=pub.distroseries.distribution, | ||
32 | 215 | distroseries=pub.distroseries, | ||
33 | 216 | pocket=pub.pocket, | ||
34 | 217 | channel=pub.channel, | ||
35 | 218 | ) | ||
36 | 219 | |||
37 | 220 | |||
38 | 207 | def find_live_source_versions(sorted_pubs): | 221 | def find_live_source_versions(sorted_pubs): |
39 | 208 | """Find versions out of Published publications that should stay live. | 222 | """Find versions out of Published publications that should stay live. |
40 | 209 | 223 | ||
41 | @@ -336,10 +350,19 @@ def find_live_binary_versions_pass_2(sorted_pubs, cache): | |||
42 | 336 | [pub.binarypackagerelease for pub in arch_indep_pubs], ['buildID']) | 350 | [pub.binarypackagerelease for pub in arch_indep_pubs], ['buildID']) |
43 | 337 | load_related(SourcePackageRelease, bpbs, ['source_package_release_id']) | 351 | load_related(SourcePackageRelease, bpbs, ['source_package_release_id']) |
44 | 338 | 352 | ||
45 | 353 | # XXX cjwatson 2022-05-01: Skip the architecture-specific check for | ||
46 | 354 | # publications from CI builds for now, until we figure out how to | ||
47 | 355 | # approximate source package releases for groups of CI builds. We don't | ||
48 | 356 | # currently expect problematic situations to come up on production; CI | ||
49 | 357 | # builds are currently only expected to be used in situations where | ||
50 | 358 | # either we don't build both architecture-specific and | ||
51 | 359 | # architecture-independent packages, or where tight dependencies between | ||
52 | 360 | # the two aren't customary. | ||
53 | 339 | reprieved_pubs = [ | 361 | reprieved_pubs = [ |
54 | 340 | pub | 362 | pub |
55 | 341 | for pub in arch_indep_pubs | 363 | for pub in arch_indep_pubs |
57 | 342 | if cache.hasArchSpecificPublications(pub)] | 364 | if pub.binarypackagerelease.ci_build_id is None and |
58 | 365 | cache.hasArchSpecificPublications(pub)] | ||
59 | 343 | 366 | ||
60 | 344 | return get_binary_versions([latest] + arch_specific_pubs + reprieved_pubs) | 367 | return get_binary_versions([latest] + arch_specific_pubs + reprieved_pubs) |
61 | 345 | 368 | ||
62 | @@ -382,9 +405,9 @@ class Dominator: | |||
63 | 382 | list we import. | 405 | list we import. |
64 | 383 | 406 | ||
65 | 384 | :param sorted_pubs: A list of publications for the same package, | 407 | :param sorted_pubs: A list of publications for the same package, |
69 | 385 | in the same archive, series, and pocket, all with status | 408 | in the same archive, series, pocket, and channel, all with |
70 | 386 | `PackagePublishingStatus.PUBLISHED`. They must be sorted from | 409 | status `PackagePublishingStatus.PUBLISHED`. They must be sorted |
71 | 387 | most current to least current, as would be the result of | 410 | from most current to least current, as would be the result of |
72 | 388 | `generalization.sortPublications`. | 411 | `generalization.sortPublications`. |
73 | 389 | :param live_versions: Iterable of versions that are still considered | 412 | :param live_versions: Iterable of versions that are still considered |
74 | 390 | "live" for this package. For any of these, the latest publication | 413 | "live" for this package. For any of these, the latest publication |
75 | @@ -458,31 +481,47 @@ class Dominator: | |||
76 | 458 | return supersede, keep, delete | 481 | return supersede, keep, delete |
77 | 459 | 482 | ||
78 | 460 | def _sortPackages(self, publications, generalization): | 483 | def _sortPackages(self, publications, generalization): |
80 | 461 | """Partition publications by package name, and sort them. | 484 | """Partition publications by package name and location, and sort them. |
81 | 462 | 485 | ||
82 | 463 | The publications are sorted from most current to least current, | 486 | The publications are sorted from most current to least current, |
84 | 464 | as required by `planPackageDomination` etc. | 487 | as required by `planPackageDomination` etc. Locations are currently |
85 | 488 | (package name, channel). | ||
86 | 465 | 489 | ||
87 | 466 | :param publications: An iterable of `SourcePackagePublishingHistory` | 490 | :param publications: An iterable of `SourcePackagePublishingHistory` |
88 | 467 | or of `BinaryPackagePublishingHistory`. | 491 | or of `BinaryPackagePublishingHistory`. |
89 | 468 | :param generalization: A `GeneralizedPublication` helper representing | 492 | :param generalization: A `GeneralizedPublication` helper representing |
90 | 469 | the kind of publications these are: source or binary. | 493 | the kind of publications these are: source or binary. |
93 | 470 | :return: A dict mapping each package name to a sorted list of | 494 | :return: A dict mapping each package location (package name, |
94 | 471 | publications from `publications`. | 495 | channel) to a sorted list of publications from `publications`. |
95 | 472 | """ | 496 | """ |
97 | 473 | pubs_by_package = defaultdict(list) | 497 | # XXX cjwatson 2022-05-19: Traditional suites (distroseries/pocket) |
98 | 498 | # are divided up in the loop in Publisher.B_dominate. However, this | ||
99 | 499 | # doesn't scale to channel-map-style suites (distroseries/channel), | ||
100 | 500 | # since there may be a very large number of channels and we don't | ||
101 | 501 | # want to have to loop over all the possible ones, so we divide | ||
102 | 502 | # those up here instead. | ||
103 | 503 | # | ||
104 | 504 | # This is definitely confusing. In the longer term, we should | ||
105 | 505 | # probably push the loop down from the publisher to here, and sort | ||
106 | 506 | # and dominate all candidates in a given archive at once: there's no | ||
107 | 507 | # particularly obvious reason not to, and it might perform better as | ||
108 | 508 | # well. | ||
109 | 509 | |||
110 | 510 | pubs_by_name_and_location = defaultdict(list) | ||
111 | 474 | for pub in publications: | 511 | for pub in publications: |
113 | 475 | pubs_by_package[generalization.getPackageName(pub)].append(pub) | 512 | name = generalization.getPackageName(pub) |
114 | 513 | location = make_package_location(pub) | ||
115 | 514 | pubs_by_name_and_location[(name, location)].append(pub) | ||
116 | 476 | 515 | ||
117 | 477 | # Sort the publication lists. This is not an in-place sort, so | 516 | # Sort the publication lists. This is not an in-place sort, so |
118 | 478 | # it involves altering the dict while we iterate it. Listify | 517 | # it involves altering the dict while we iterate it. Listify |
120 | 479 | # the keys so that we can be sure that we're not altering the | 518 | # the items so that we can be sure that we're not altering the |
121 | 480 | # iteration order while iteration is underway. | 519 | # iteration order while iteration is underway. |
125 | 481 | for package in list(pubs_by_package.keys()): | 520 | for (name, location), pubs in list(pubs_by_name_and_location.items()): |
126 | 482 | pubs_by_package[package] = generalization.sortPublications( | 521 | pubs_by_name_and_location[(name, location)] = ( |
127 | 483 | pubs_by_package[package]) | 522 | generalization.sortPublications(pubs)) |
128 | 484 | 523 | ||
130 | 485 | return pubs_by_package | 524 | return pubs_by_name_and_location |
131 | 486 | 525 | ||
132 | 487 | def _setScheduledDeletionDate(self, pub_record): | 526 | def _setScheduledDeletionDate(self, pub_record): |
133 | 488 | """Set the scheduleddeletiondate on a publishing record. | 527 | """Set the scheduleddeletiondate on a publishing record. |
134 | @@ -541,7 +580,10 @@ class Dominator: | |||
135 | 541 | BinaryPackagePublishingHistory.binarypackagerelease == | 580 | BinaryPackagePublishingHistory.binarypackagerelease == |
136 | 542 | BinaryPackageRelease.id, | 581 | BinaryPackageRelease.id, |
137 | 543 | BinaryPackageRelease.build == BinaryPackageBuild.id, | 582 | BinaryPackageRelease.build == BinaryPackageBuild.id, |
139 | 544 | BinaryPackagePublishingHistory.pocket == pub_record.pocket) | 583 | BinaryPackagePublishingHistory.pocket == pub_record.pocket, |
140 | 584 | Not(IsDistinctFrom( | ||
141 | 585 | BinaryPackagePublishingHistory._channel, | ||
142 | 586 | pub_record._channel))) | ||
143 | 545 | 587 | ||
144 | 546 | # There is at least one non-removed binary to consider | 588 | # There is at least one non-removed binary to consider |
145 | 547 | if not considered_binaries.is_empty(): | 589 | if not considered_binaries.is_empty(): |
146 | @@ -552,6 +594,7 @@ class Dominator: | |||
147 | 552 | SourcePackagePublishingHistory, | 594 | SourcePackagePublishingHistory, |
148 | 553 | distroseries=pub_record.distroseries, | 595 | distroseries=pub_record.distroseries, |
149 | 554 | pocket=pub_record.pocket, | 596 | pocket=pub_record.pocket, |
150 | 597 | channel=pub_record.channel, | ||
151 | 555 | status=PackagePublishingStatus.PUBLISHED, | 598 | status=PackagePublishingStatus.PUBLISHED, |
152 | 556 | archive=self.archive, | 599 | archive=self.archive, |
153 | 557 | sourcepackagerelease=srcpkg_release) | 600 | sourcepackagerelease=srcpkg_release) |
154 | @@ -588,7 +631,8 @@ class Dominator: | |||
155 | 588 | ] | 631 | ] |
156 | 589 | candidate_binary_names = Select( | 632 | candidate_binary_names = Select( |
157 | 590 | BPPH.binarypackagenameID, And(*bpph_location_clauses), | 633 | BPPH.binarypackagenameID, And(*bpph_location_clauses), |
159 | 591 | group_by=BPPH.binarypackagenameID, having=(Count() > 1)) | 634 | group_by=(BPPH.binarypackagenameID, BPPH._channel), |
160 | 635 | having=(Count() > 1)) | ||
161 | 592 | main_clauses = bpph_location_clauses + [ | 636 | main_clauses = bpph_location_clauses + [ |
162 | 593 | BPR.id == BPPH.binarypackagereleaseID, | 637 | BPR.id == BPPH.binarypackagereleaseID, |
163 | 594 | BPR.binarypackagenameID.is_in(candidate_binary_names), | 638 | BPR.binarypackagenameID.is_in(candidate_binary_names), |
164 | @@ -664,13 +708,14 @@ class Dominator: | |||
165 | 664 | bins = self.findBinariesForDomination(distroarchseries, pocket) | 708 | bins = self.findBinariesForDomination(distroarchseries, pocket) |
166 | 665 | sorted_packages = self._sortPackages(bins, generalization) | 709 | sorted_packages = self._sortPackages(bins, generalization) |
167 | 666 | self.logger.info("Planning domination of binaries...") | 710 | self.logger.info("Planning domination of binaries...") |
170 | 667 | for name, pubs in sorted_packages.items(): | 711 | for (name, location), pubs in sorted_packages.items(): |
171 | 668 | self.logger.debug("Planning domination of %s" % name) | 712 | self.logger.debug( |
172 | 713 | "Planning domination of %s in %s" % (name, location)) | ||
173 | 669 | assert len(pubs) > 0, "Dominating zero binaries!" | 714 | assert len(pubs) > 0, "Dominating zero binaries!" |
174 | 670 | live_versions = find_live_binary_versions_pass_1(pubs) | 715 | live_versions = find_live_binary_versions_pass_1(pubs) |
175 | 671 | plan(pubs, live_versions) | 716 | plan(pubs, live_versions) |
176 | 672 | if contains_arch_indep(pubs): | 717 | if contains_arch_indep(pubs): |
178 | 673 | packages_w_arch_indep.add(name) | 718 | packages_w_arch_indep.add((name, location)) |
179 | 674 | 719 | ||
180 | 675 | execute_plan() | 720 | execute_plan() |
181 | 676 | 721 | ||
182 | @@ -692,9 +737,11 @@ class Dominator: | |||
183 | 692 | bins = self.findBinariesForDomination(distroarchseries, pocket) | 737 | bins = self.findBinariesForDomination(distroarchseries, pocket) |
184 | 693 | sorted_packages = self._sortPackages(bins, generalization) | 738 | sorted_packages = self._sortPackages(bins, generalization) |
185 | 694 | self.logger.info("Planning domination of binaries...(2nd pass)") | 739 | self.logger.info("Planning domination of binaries...(2nd pass)") |
189 | 695 | for name in packages_w_arch_indep.intersection(sorted_packages): | 740 | for name, location in packages_w_arch_indep.intersection( |
190 | 696 | pubs = sorted_packages[name] | 741 | sorted_packages): |
191 | 697 | self.logger.debug("Planning domination of %s" % name) | 742 | pubs = sorted_packages[(name, location)] |
192 | 743 | self.logger.debug( | ||
193 | 744 | "Planning domination of %s in %s" % (name, location)) | ||
194 | 698 | assert len(pubs) > 0, "Dominating zero binaries in 2nd pass!" | 745 | assert len(pubs) > 0, "Dominating zero binaries in 2nd pass!" |
195 | 699 | live_versions = find_live_binary_versions_pass_2( | 746 | live_versions = find_live_binary_versions_pass_2( |
196 | 700 | pubs, reprieve_cache) | 747 | pubs, reprieve_cache) |
197 | @@ -732,7 +779,7 @@ class Dominator: | |||
198 | 732 | candidate_source_names = Select( | 779 | candidate_source_names = Select( |
199 | 733 | SPPH.sourcepackagenameID, | 780 | SPPH.sourcepackagenameID, |
200 | 734 | And(join_spph_spr(), spph_location_clauses), | 781 | And(join_spph_spr(), spph_location_clauses), |
202 | 735 | group_by=SPPH.sourcepackagenameID, | 782 | group_by=(SPPH.sourcepackagenameID, SPPH._channel), |
203 | 736 | having=(Count() > 1)) | 783 | having=(Count() > 1)) |
204 | 737 | 784 | ||
205 | 738 | # We'll also access the SourcePackageReleases associated with | 785 | # We'll also access the SourcePackageReleases associated with |
206 | @@ -769,8 +816,8 @@ class Dominator: | |||
207 | 769 | delete = [] | 816 | delete = [] |
208 | 770 | 817 | ||
209 | 771 | self.logger.debug("Dominating sources...") | 818 | self.logger.debug("Dominating sources...") |
212 | 772 | for name, pubs in sorted_packages.items(): | 819 | for (name, location), pubs in sorted_packages.items(): |
213 | 773 | self.logger.debug("Dominating %s" % name) | 820 | self.logger.debug("Dominating %s in %s" % (name, location)) |
214 | 774 | assert len(pubs) > 0, "Dominating zero sources!" | 821 | assert len(pubs) > 0, "Dominating zero sources!" |
215 | 775 | live_versions = find_live_source_versions(pubs) | 822 | live_versions = find_live_source_versions(pubs) |
216 | 776 | cur_supersede, _, cur_delete = self.planPackageDomination( | 823 | cur_supersede, _, cur_delete = self.planPackageDomination( |
217 | diff --git a/lib/lp/archivepublisher/publishing.py b/lib/lp/archivepublisher/publishing.py | |||
218 | index 0639716..69fb7e3 100644 | |||
219 | --- a/lib/lp/archivepublisher/publishing.py | |||
220 | +++ b/lib/lp/archivepublisher/publishing.py | |||
221 | @@ -680,6 +680,9 @@ class Publisher: | |||
222 | 680 | judgejudy = Dominator(self.log, self.archive) | 680 | judgejudy = Dominator(self.log, self.archive) |
223 | 681 | for distroseries in self.distro.series: | 681 | for distroseries in self.distro.series: |
224 | 682 | for pocket in self.archive.getPockets(): | 682 | for pocket in self.archive.getPockets(): |
225 | 683 | # XXX cjwatson 2022-05-19: Channels are handled in the | ||
226 | 684 | # dominator instead; see the comment in | ||
227 | 685 | # Dominator._sortPackages. | ||
228 | 683 | if not self.isAllowed(distroseries, pocket): | 686 | if not self.isAllowed(distroseries, pocket): |
229 | 684 | continue | 687 | continue |
230 | 685 | if not force_domination: | 688 | if not force_domination: |
231 | diff --git a/lib/lp/archivepublisher/tests/test_dominator.py b/lib/lp/archivepublisher/tests/test_dominator.py | |||
232 | 686 | old mode 100755 | 689 | old mode 100755 |
233 | 687 | new mode 100644 | 690 | new mode 100644 |
234 | index 01c56e4..23d5468 | |||
235 | --- a/lib/lp/archivepublisher/tests/test_dominator.py | |||
236 | +++ b/lib/lp/archivepublisher/tests/test_dominator.py | |||
237 | @@ -30,7 +30,11 @@ from lp.archivepublisher.publishing import Publisher | |||
238 | 30 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 30 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
239 | 31 | from lp.registry.interfaces.series import SeriesStatus | 31 | from lp.registry.interfaces.series import SeriesStatus |
240 | 32 | from lp.services.log.logger import DevNullLogger | 32 | from lp.services.log.logger import DevNullLogger |
242 | 33 | from lp.soyuz.enums import PackagePublishingStatus | 33 | from lp.soyuz.adapters.packagelocation import PackageLocation |
243 | 34 | from lp.soyuz.enums import ( | ||
244 | 35 | BinaryPackageFormat, | ||
245 | 36 | PackagePublishingStatus, | ||
246 | 37 | ) | ||
247 | 34 | from lp.soyuz.interfaces.publishing import ( | 38 | from lp.soyuz.interfaces.publishing import ( |
248 | 35 | IPublishingSet, | 39 | IPublishingSet, |
249 | 36 | ISourcePackagePublishingHistory, | 40 | ISourcePackagePublishingHistory, |
250 | @@ -168,29 +172,39 @@ class TestDominator(TestNativePublishingBase): | |||
251 | 168 | """Domination asserts for non-empty input list.""" | 172 | """Domination asserts for non-empty input list.""" |
252 | 169 | with lp_dbuser(): | 173 | with lp_dbuser(): |
253 | 170 | distroseries = self.factory.makeDistroArchSeries().distroseries | 174 | distroseries = self.factory.makeDistroArchSeries().distroseries |
254 | 175 | pocket = self.factory.getAnyPocket() | ||
255 | 171 | package = self.factory.makeBinaryPackageName() | 176 | package = self.factory.makeBinaryPackageName() |
256 | 177 | location = PackageLocation( | ||
257 | 178 | archive=self.ubuntutest.main_archive, | ||
258 | 179 | distribution=distroseries.distribution, distroseries=distroseries, | ||
259 | 180 | pocket=pocket) | ||
260 | 172 | dominator = Dominator(self.logger, self.ubuntutest.main_archive) | 181 | dominator = Dominator(self.logger, self.ubuntutest.main_archive) |
262 | 173 | dominator._sortPackages = FakeMethod({package.name: []}) | 182 | dominator._sortPackages = FakeMethod({(package.name, location): []}) |
263 | 174 | # This isn't a really good exception. It should probably be | 183 | # This isn't a really good exception. It should probably be |
264 | 175 | # something more indicative of bad input. | 184 | # something more indicative of bad input. |
265 | 176 | self.assertRaises( | 185 | self.assertRaises( |
266 | 177 | AssertionError, | 186 | AssertionError, |
267 | 178 | dominator.dominateBinaries, | 187 | dominator.dominateBinaries, |
269 | 179 | distroseries, self.factory.getAnyPocket()) | 188 | distroseries, pocket) |
270 | 180 | 189 | ||
271 | 181 | def test_dominateSources_rejects_empty_publication_list(self): | 190 | def test_dominateSources_rejects_empty_publication_list(self): |
272 | 182 | """Domination asserts for non-empty input list.""" | 191 | """Domination asserts for non-empty input list.""" |
273 | 183 | with lp_dbuser(): | 192 | with lp_dbuser(): |
274 | 184 | distroseries = self.factory.makeDistroSeries() | 193 | distroseries = self.factory.makeDistroSeries() |
275 | 194 | pocket = self.factory.getAnyPocket() | ||
276 | 185 | package = self.factory.makeSourcePackageName() | 195 | package = self.factory.makeSourcePackageName() |
277 | 196 | location = PackageLocation( | ||
278 | 197 | archive=self.ubuntutest.main_archive, | ||
279 | 198 | distribution=distroseries.distribution, distroseries=distroseries, | ||
280 | 199 | pocket=pocket) | ||
281 | 186 | dominator = Dominator(self.logger, self.ubuntutest.main_archive) | 200 | dominator = Dominator(self.logger, self.ubuntutest.main_archive) |
283 | 187 | dominator._sortPackages = FakeMethod({package.name: []}) | 201 | dominator._sortPackages = FakeMethod({(package.name, location): []}) |
284 | 188 | # This isn't a really good exception. It should probably be | 202 | # This isn't a really good exception. It should probably be |
285 | 189 | # something more indicative of bad input. | 203 | # something more indicative of bad input. |
286 | 190 | self.assertRaises( | 204 | self.assertRaises( |
287 | 191 | AssertionError, | 205 | AssertionError, |
288 | 192 | dominator.dominateSources, | 206 | dominator.dominateSources, |
290 | 193 | distroseries, self.factory.getAnyPocket()) | 207 | distroseries, pocket) |
291 | 194 | 208 | ||
292 | 195 | def test_archall_domination(self): | 209 | def test_archall_domination(self): |
293 | 196 | # Arch-all binaries should not be dominated when a new source | 210 | # Arch-all binaries should not be dominated when a new source |
294 | @@ -398,6 +412,51 @@ class TestDominator(TestNativePublishingBase): | |||
295 | 398 | for pub in overrides_2: | 412 | for pub in overrides_2: |
296 | 399 | self.assertEqual(PackagePublishingStatus.PUBLISHED, pub.status) | 413 | self.assertEqual(PackagePublishingStatus.PUBLISHED, pub.status) |
297 | 400 | 414 | ||
298 | 415 | def test_dominate_by_channel(self): | ||
299 | 416 | # Publications only dominate other publications in the same channel. | ||
300 | 417 | # (Currently only tested for binary publications.) | ||
301 | 418 | with lp_dbuser(): | ||
302 | 419 | archive = self.factory.makeArchive() | ||
303 | 420 | distroseries = self.factory.makeDistroSeries( | ||
304 | 421 | distribution=archive.distribution) | ||
305 | 422 | das = self.factory.makeDistroArchSeries(distroseries=distroseries) | ||
306 | 423 | repository = self.factory.makeGitRepository( | ||
307 | 424 | target=self.factory.makeDistributionSourcePackage( | ||
308 | 425 | distribution=archive.distribution)) | ||
309 | 426 | ci_builds = [ | ||
310 | 427 | self.factory.makeCIBuild( | ||
311 | 428 | git_repository=repository, distro_arch_series=das) | ||
312 | 429 | for _ in range(3)] | ||
313 | 430 | bpn = self.factory.makeBinaryPackageName() | ||
314 | 431 | bprs = [ | ||
315 | 432 | self.factory.makeBinaryPackageRelease( | ||
316 | 433 | binarypackagename=bpn, version=version, ci_build=ci_build, | ||
317 | 434 | binpackageformat=BinaryPackageFormat.WHL) | ||
318 | 435 | for version, ci_build in zip(("1.0", "1.1", "1.2"), ci_builds)] | ||
319 | 436 | stable_bpphs = [ | ||
320 | 437 | self.factory.makeBinaryPackagePublishingHistory( | ||
321 | 438 | binarypackagerelease=bpr, archive=archive, | ||
322 | 439 | distroarchseries=das, status=PackagePublishingStatus.PUBLISHED, | ||
323 | 440 | pocket=PackagePublishingPocket.RELEASE, channel="stable") | ||
324 | 441 | for bpr in bprs[:2]] | ||
325 | 442 | candidate_bpph = self.factory.makeBinaryPackagePublishingHistory( | ||
326 | 443 | binarypackagerelease=bprs[2], archive=archive, | ||
327 | 444 | distroarchseries=das, status=PackagePublishingStatus.PUBLISHED, | ||
328 | 445 | pocket=PackagePublishingPocket.RELEASE, channel="candidate") | ||
329 | 446 | |||
330 | 447 | dominator = Dominator(self.logger, archive) | ||
331 | 448 | dominator.judgeAndDominate( | ||
332 | 449 | distroseries, PackagePublishingPocket.RELEASE) | ||
333 | 450 | |||
334 | 451 | # The older of the two stable publications is superseded, while the | ||
335 | 452 | # current stable publication and the candidate publication are left | ||
336 | 453 | # alone. | ||
337 | 454 | self.checkPublication( | ||
338 | 455 | stable_bpphs[0], PackagePublishingStatus.SUPERSEDED) | ||
339 | 456 | self.checkPublications( | ||
340 | 457 | (stable_bpphs[1], candidate_bpph), | ||
341 | 458 | PackagePublishingStatus.PUBLISHED) | ||
342 | 459 | |||
343 | 401 | 460 | ||
344 | 402 | class TestDomination(TestNativePublishingBase): | 461 | class TestDomination(TestNativePublishingBase): |
345 | 403 | """Test overall domination procedure.""" | 462 | """Test overall domination procedure.""" |
346 | @@ -1315,7 +1374,7 @@ class TestArchSpecificPublicationsCache(TestCaseWithFactory): | |||
347 | 1315 | return removeSecurityProxy(self.factory.makeSourcePackageRelease()) | 1374 | return removeSecurityProxy(self.factory.makeSourcePackageRelease()) |
348 | 1316 | 1375 | ||
349 | 1317 | def makeBPPH(self, spr=None, arch_specific=True, archive=None, | 1376 | def makeBPPH(self, spr=None, arch_specific=True, archive=None, |
351 | 1318 | distroseries=None): | 1377 | distroseries=None, binpackageformat=None, channel=None): |
352 | 1319 | """Create a `BinaryPackagePublishingHistory`.""" | 1378 | """Create a `BinaryPackagePublishingHistory`.""" |
353 | 1320 | if spr is None: | 1379 | if spr is None: |
354 | 1321 | spr = self.makeSPR() | 1380 | spr = self.makeSPR() |
355 | @@ -1323,12 +1382,13 @@ class TestArchSpecificPublicationsCache(TestCaseWithFactory): | |||
356 | 1323 | bpb = self.factory.makeBinaryPackageBuild( | 1382 | bpb = self.factory.makeBinaryPackageBuild( |
357 | 1324 | source_package_release=spr, distroarchseries=das) | 1383 | source_package_release=spr, distroarchseries=das) |
358 | 1325 | bpr = self.factory.makeBinaryPackageRelease( | 1384 | bpr = self.factory.makeBinaryPackageRelease( |
360 | 1326 | build=bpb, architecturespecific=arch_specific) | 1385 | build=bpb, binpackageformat=binpackageformat, |
361 | 1386 | architecturespecific=arch_specific) | ||
362 | 1327 | return removeSecurityProxy( | 1387 | return removeSecurityProxy( |
363 | 1328 | self.factory.makeBinaryPackagePublishingHistory( | 1388 | self.factory.makeBinaryPackagePublishingHistory( |
364 | 1329 | binarypackagerelease=bpr, archive=archive, | 1389 | binarypackagerelease=bpr, archive=archive, |
365 | 1330 | distroarchseries=das, pocket=PackagePublishingPocket.UPDATES, | 1390 | distroarchseries=das, pocket=PackagePublishingPocket.UPDATES, |
367 | 1331 | status=PackagePublishingStatus.PUBLISHED)) | 1391 | status=PackagePublishingStatus.PUBLISHED, channel=channel)) |
368 | 1332 | 1392 | ||
369 | 1333 | def test_getKey_is_consistent_and_distinguishing(self): | 1393 | def test_getKey_is_consistent_and_distinguishing(self): |
370 | 1334 | # getKey consistently returns the same key for the same BPPH, | 1394 | # getKey consistently returns the same key for the same BPPH, |
371 | @@ -1351,14 +1411,19 @@ class TestArchSpecificPublicationsCache(TestCaseWithFactory): | |||
372 | 1351 | spr, arch_specific=False, archive=dependent.archive, | 1411 | spr, arch_specific=False, archive=dependent.archive, |
373 | 1352 | distroseries=dependent.distroseries) | 1412 | distroseries=dependent.distroseries) |
374 | 1353 | bpph2 = self.makeBPPH(arch_specific=False) | 1413 | bpph2 = self.makeBPPH(arch_specific=False) |
375 | 1414 | bpph3 = self.makeBPPH( | ||
376 | 1415 | arch_specific=False, binpackageformat=BinaryPackageFormat.WHL, | ||
377 | 1416 | channel="edge") | ||
378 | 1354 | cache = self.makeCache() | 1417 | cache = self.makeCache() |
379 | 1355 | self.assertEqual( | 1418 | self.assertEqual( |
381 | 1356 | [True, True, False, False], | 1419 | [True, True, False, False, False, False], |
382 | 1357 | [ | 1420 | [ |
383 | 1358 | cache.hasArchSpecificPublications(bpph1), | 1421 | cache.hasArchSpecificPublications(bpph1), |
384 | 1359 | cache.hasArchSpecificPublications(bpph1), | 1422 | cache.hasArchSpecificPublications(bpph1), |
385 | 1360 | cache.hasArchSpecificPublications(bpph2), | 1423 | cache.hasArchSpecificPublications(bpph2), |
386 | 1361 | cache.hasArchSpecificPublications(bpph2), | 1424 | cache.hasArchSpecificPublications(bpph2), |
387 | 1425 | cache.hasArchSpecificPublications(bpph3), | ||
388 | 1426 | cache.hasArchSpecificPublications(bpph3), | ||
389 | 1362 | ]) | 1427 | ]) |
390 | 1363 | 1428 | ||
391 | 1364 | def test_hasArchSpecificPublications_caches_results(self): | 1429 | def test_hasArchSpecificPublications_caches_results(self): |
392 | diff --git a/lib/lp/code/interfaces/cibuild.py b/lib/lp/code/interfaces/cibuild.py | |||
393 | index 6146dfe..fc7a197 100644 | |||
394 | --- a/lib/lp/code/interfaces/cibuild.py | |||
395 | +++ b/lib/lp/code/interfaces/cibuild.py | |||
396 | @@ -177,6 +177,14 @@ class ICIBuildView(IPackageBuildView, IPrivacy): | |||
397 | 177 | :return: A collection of URLs for this build. | 177 | :return: A collection of URLs for this build. |
398 | 178 | """ | 178 | """ |
399 | 179 | 179 | ||
400 | 180 | def createBinaryPackageRelease( | ||
401 | 181 | binarypackagename, version, summary, description, binpackageformat, | ||
402 | 182 | architecturespecific, installedsize=None, homepage=None): | ||
403 | 183 | """Create and return a `BinaryPackageRelease` for this CI build. | ||
404 | 184 | |||
405 | 185 | The new binary package release will be linked to this build. | ||
406 | 186 | """ | ||
407 | 187 | |||
408 | 180 | 188 | ||
409 | 181 | class ICIBuildEdit(IBuildFarmJobEdit): | 189 | class ICIBuildEdit(IBuildFarmJobEdit): |
410 | 182 | """`ICIBuild` methods that require launchpad.Edit.""" | 190 | """`ICIBuild` methods that require launchpad.Edit.""" |
411 | diff --git a/lib/lp/code/model/cibuild.py b/lib/lp/code/model/cibuild.py | |||
412 | index d789a95..4e1ddbd 100644 | |||
413 | --- a/lib/lp/code/model/cibuild.py | |||
414 | +++ b/lib/lp/code/model/cibuild.py | |||
415 | @@ -85,6 +85,7 @@ from lp.services.macaroons.interfaces import ( | |||
416 | 85 | ) | 85 | ) |
417 | 86 | from lp.services.macaroons.model import MacaroonIssuerBase | 86 | from lp.services.macaroons.model import MacaroonIssuerBase |
418 | 87 | from lp.services.propertycache import cachedproperty | 87 | from lp.services.propertycache import cachedproperty |
419 | 88 | from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease | ||
420 | 88 | from lp.soyuz.model.distroarchseries import DistroArchSeries | 89 | from lp.soyuz.model.distroarchseries import DistroArchSeries |
421 | 89 | 90 | ||
422 | 90 | 91 | ||
423 | @@ -465,6 +466,18 @@ class CIBuild(PackageBuildMixin, StormBase): | |||
424 | 465 | """See `IPackageBuild`.""" | 466 | """See `IPackageBuild`.""" |
425 | 466 | # We don't currently send any notifications. | 467 | # We don't currently send any notifications. |
426 | 467 | 468 | ||
427 | 469 | def createBinaryPackageRelease( | ||
428 | 470 | self, binarypackagename, version, summary, description, | ||
429 | 471 | binpackageformat, architecturespecific, installedsize=None, | ||
430 | 472 | homepage=None): | ||
431 | 473 | """See `ICIBuild`.""" | ||
432 | 474 | return BinaryPackageRelease( | ||
433 | 475 | ci_build=self, binarypackagename=binarypackagename, | ||
434 | 476 | version=version, summary=summary, description=description, | ||
435 | 477 | binpackageformat=binpackageformat, | ||
436 | 478 | architecturespecific=architecturespecific, | ||
437 | 479 | installedsize=installedsize, homepage=homepage) | ||
438 | 480 | |||
439 | 468 | 481 | ||
440 | 469 | @implementer(ICIBuildSet) | 482 | @implementer(ICIBuildSet) |
441 | 470 | class CIBuildSet(SpecificBuildFarmJobSourceMixin): | 483 | class CIBuildSet(SpecificBuildFarmJobSourceMixin): |
442 | diff --git a/lib/lp/code/model/tests/test_cibuild.py b/lib/lp/code/model/tests/test_cibuild.py | |||
443 | index 899b09b..b7e0ca5 100644 | |||
444 | --- a/lib/lp/code/model/tests/test_cibuild.py | |||
445 | +++ b/lib/lp/code/model/tests/test_cibuild.py | |||
446 | @@ -70,6 +70,7 @@ from lp.services.macaroons.interfaces import IMacaroonIssuer | |||
447 | 70 | from lp.services.macaroons.testing import MacaroonTestMixin | 70 | from lp.services.macaroons.testing import MacaroonTestMixin |
448 | 71 | from lp.services.propertycache import clear_property_cache | 71 | from lp.services.propertycache import clear_property_cache |
449 | 72 | from lp.services.webapp.interfaces import OAuthPermission | 72 | from lp.services.webapp.interfaces import OAuthPermission |
450 | 73 | from lp.soyuz.enums import BinaryPackageFormat | ||
451 | 73 | from lp.testing import ( | 74 | from lp.testing import ( |
452 | 74 | ANONYMOUS, | 75 | ANONYMOUS, |
453 | 75 | api_url, | 76 | api_url, |
454 | @@ -399,6 +400,24 @@ class TestCIBuild(TestCaseWithFactory): | |||
455 | 399 | commit_sha1=build.commit_sha1, | 400 | commit_sha1=build.commit_sha1, |
456 | 400 | ci_build=build)) | 401 | ci_build=build)) |
457 | 401 | 402 | ||
458 | 403 | def test_createBinaryPackageRelease(self): | ||
459 | 404 | build = self.factory.makeCIBuild() | ||
460 | 405 | bpn = self.factory.makeBinaryPackageName() | ||
461 | 406 | bpr = build.createBinaryPackageRelease( | ||
462 | 407 | bpn, "1.0", "test summary", "test description", | ||
463 | 408 | BinaryPackageFormat.WHL, False, installedsize=1024, | ||
464 | 409 | homepage="https://example.com/") | ||
465 | 410 | self.assertThat(bpr, MatchesStructure( | ||
466 | 411 | binarypackagename=Equals(bpn), | ||
467 | 412 | version=Equals("1.0"), | ||
468 | 413 | summary=Equals("test summary"), | ||
469 | 414 | description=Equals("test description"), | ||
470 | 415 | binpackageformat=Equals(BinaryPackageFormat.WHL), | ||
471 | 416 | architecturespecific=Is(False), | ||
472 | 417 | installedsize=Equals(1024), | ||
473 | 418 | homepage=Equals("https://example.com/"), | ||
474 | 419 | )) | ||
475 | 420 | |||
476 | 402 | 421 | ||
477 | 403 | class TestCIBuildSet(TestCaseWithFactory): | 422 | class TestCIBuildSet(TestCaseWithFactory): |
478 | 404 | 423 | ||
479 | diff --git a/lib/lp/soyuz/adapters/packagelocation.py b/lib/lp/soyuz/adapters/packagelocation.py | |||
480 | index 6644f1e..2cf1cb4 100644 | |||
481 | --- a/lib/lp/soyuz/adapters/packagelocation.py | |||
482 | +++ b/lib/lp/soyuz/adapters/packagelocation.py | |||
483 | @@ -29,9 +29,10 @@ class PackageLocation: | |||
484 | 29 | pocket = None | 29 | pocket = None |
485 | 30 | component = None | 30 | component = None |
486 | 31 | packagesets = None | 31 | packagesets = None |
487 | 32 | channel = None | ||
488 | 32 | 33 | ||
489 | 33 | def __init__(self, archive, distribution, distroseries, pocket, | 34 | def __init__(self, archive, distribution, distroseries, pocket, |
491 | 34 | component=None, packagesets=None): | 35 | component=None, packagesets=None, channel=None): |
492 | 35 | """Initialize the PackageLocation from the given parameters.""" | 36 | """Initialize the PackageLocation from the given parameters.""" |
493 | 36 | self.archive = archive | 37 | self.archive = archive |
494 | 37 | self.distribution = distribution | 38 | self.distribution = distribution |
495 | @@ -39,6 +40,7 @@ class PackageLocation: | |||
496 | 39 | self.pocket = pocket | 40 | self.pocket = pocket |
497 | 40 | self.component = component | 41 | self.component = component |
498 | 41 | self.packagesets = packagesets or [] | 42 | self.packagesets = packagesets or [] |
499 | 43 | self.channel = channel | ||
500 | 42 | 44 | ||
501 | 43 | def __eq__(self, other): | 45 | def __eq__(self, other): |
502 | 44 | if (self.distribution == other.distribution and | 46 | if (self.distribution == other.distribution and |
503 | @@ -46,10 +48,22 @@ class PackageLocation: | |||
504 | 46 | self.distroseries == other.distroseries and | 48 | self.distroseries == other.distroseries and |
505 | 47 | self.component == other.component and | 49 | self.component == other.component and |
506 | 48 | self.pocket == other.pocket and | 50 | self.pocket == other.pocket and |
508 | 49 | self.packagesets == other.packagesets): | 51 | self.packagesets == other.packagesets and |
509 | 52 | self.channel == other.channel): | ||
510 | 50 | return True | 53 | return True |
511 | 51 | return False | 54 | return False |
512 | 52 | 55 | ||
513 | 56 | def __hash__(self): | ||
514 | 57 | return hash(( | ||
515 | 58 | self.archive, | ||
516 | 59 | self.distribution, | ||
517 | 60 | self.distroseries, | ||
518 | 61 | self.pocket, | ||
519 | 62 | self.component, | ||
520 | 63 | None if self.packagesets is None else tuple(self.packagesets), | ||
521 | 64 | self.channel, | ||
522 | 65 | )) | ||
523 | 66 | |||
524 | 53 | def __str__(self): | 67 | def __str__(self): |
525 | 54 | result = '%s: %s-%s' % ( | 68 | result = '%s: %s-%s' % ( |
526 | 55 | self.archive.reference, self.distroseries.name, self.pocket.name) | 69 | self.archive.reference, self.distroseries.name, self.pocket.name) |
527 | @@ -61,6 +75,9 @@ class PackageLocation: | |||
528 | 61 | result += ' [%s]' % ( | 75 | result += ' [%s]' % ( |
529 | 62 | ", ".join([str(p.name) for p in self.packagesets]),) | 76 | ", ".join([str(p.name) for p in self.packagesets]),) |
530 | 63 | 77 | ||
531 | 78 | if self.channel is not None: | ||
532 | 79 | result += ' {%s}' % self.channel | ||
533 | 80 | |||
534 | 64 | return result | 81 | return result |
535 | 65 | 82 | ||
536 | 66 | 83 | ||
537 | @@ -70,7 +87,7 @@ class PackageLocationError(Exception): | |||
538 | 70 | 87 | ||
539 | 71 | def build_package_location(distribution_name, suite=None, purpose=None, | 88 | def build_package_location(distribution_name, suite=None, purpose=None, |
540 | 72 | person_name=None, archive_name=None, | 89 | person_name=None, archive_name=None, |
542 | 73 | packageset_names=None): | 90 | packageset_names=None, channel=None): |
543 | 74 | """Convenience function to build PackageLocation objects.""" | 91 | """Convenience function to build PackageLocation objects.""" |
544 | 75 | 92 | ||
545 | 76 | # XXX kiko 2007-10-24: | 93 | # XXX kiko 2007-10-24: |
546 | @@ -143,6 +160,10 @@ def build_package_location(distribution_name, suite=None, purpose=None, | |||
547 | 143 | distroseries = distribution.currentseries | 160 | distroseries = distribution.currentseries |
548 | 144 | pocket = PackagePublishingPocket.RELEASE | 161 | pocket = PackagePublishingPocket.RELEASE |
549 | 145 | 162 | ||
550 | 163 | if pocket != PackagePublishingPocket.RELEASE and channel is not None: | ||
551 | 164 | raise PackageLocationError( | ||
552 | 165 | "Channels may only be used with the RELEASE pocket.") | ||
553 | 166 | |||
554 | 146 | packagesets = [] | 167 | packagesets = [] |
555 | 147 | if packageset_names: | 168 | if packageset_names: |
556 | 148 | packageset_set = getUtility(IPackagesetSet) | 169 | packageset_set = getUtility(IPackagesetSet) |
557 | @@ -155,5 +176,6 @@ def build_package_location(distribution_name, suite=None, purpose=None, | |||
558 | 155 | "Could not find packageset %s" % err) | 176 | "Could not find packageset %s" % err) |
559 | 156 | packagesets.append(packageset) | 177 | packagesets.append(packageset) |
560 | 157 | 178 | ||
563 | 158 | return PackageLocation(archive, distribution, distroseries, pocket, | 179 | return PackageLocation( |
564 | 159 | packagesets=packagesets) | 180 | archive, distribution, distroseries, pocket, |
565 | 181 | packagesets=packagesets, channel=channel) | ||
566 | diff --git a/lib/lp/soyuz/adapters/tests/test_packagelocation.py b/lib/lp/soyuz/adapters/tests/test_packagelocation.py | |||
567 | index 3d45f14..9bbef24 100644 | |||
568 | --- a/lib/lp/soyuz/adapters/tests/test_packagelocation.py | |||
569 | +++ b/lib/lp/soyuz/adapters/tests/test_packagelocation.py | |||
570 | @@ -22,11 +22,12 @@ class TestPackageLocation(TestCaseWithFactory): | |||
571 | 22 | 22 | ||
572 | 23 | def getPackageLocation(self, distribution_name='ubuntu', suite=None, | 23 | def getPackageLocation(self, distribution_name='ubuntu', suite=None, |
573 | 24 | purpose=None, person_name=None, | 24 | purpose=None, person_name=None, |
575 | 25 | archive_name=None, packageset_names=None): | 25 | archive_name=None, packageset_names=None, |
576 | 26 | channel=None): | ||
577 | 26 | """Use a helper method to setup a `PackageLocation` object.""" | 27 | """Use a helper method to setup a `PackageLocation` object.""" |
578 | 27 | return build_package_location( | 28 | return build_package_location( |
579 | 28 | distribution_name, suite, purpose, person_name, archive_name, | 29 | distribution_name, suite, purpose, person_name, archive_name, |
581 | 29 | packageset_names=packageset_names) | 30 | packageset_names=packageset_names, channel=channel) |
582 | 30 | 31 | ||
583 | 31 | def testSetupLocationForCOPY(self): | 32 | def testSetupLocationForCOPY(self): |
584 | 32 | """`PackageLocation` for COPY archives.""" | 33 | """`PackageLocation` for COPY archives.""" |
585 | @@ -150,6 +151,15 @@ class TestPackageLocation(TestCaseWithFactory): | |||
586 | 150 | distribution_name='debian', | 151 | distribution_name='debian', |
587 | 151 | packageset_names=[packageset_name, "unknown"]) | 152 | packageset_names=[packageset_name, "unknown"]) |
588 | 152 | 153 | ||
589 | 154 | def test_build_package_location_with_channel_outside_release_pocket(self): | ||
590 | 155 | """It doesn't make sense to use non-RELEASE pockets with channels.""" | ||
591 | 156 | self.assertRaisesWithContent( | ||
592 | 157 | PackageLocationError, | ||
593 | 158 | "Channels may only be used with the RELEASE pocket.", | ||
594 | 159 | self.getPackageLocation, | ||
595 | 160 | suite="warty-security", | ||
596 | 161 | channel="stable") | ||
597 | 162 | |||
598 | 153 | def testSetupLocationPPANotMatchingDistribution(self): | 163 | def testSetupLocationPPANotMatchingDistribution(self): |
599 | 154 | """`PackageLocationError` is raised when PPA does not match the | 164 | """`PackageLocationError` is raised when PPA does not match the |
600 | 155 | distribution.""" | 165 | distribution.""" |
601 | @@ -214,6 +224,14 @@ class TestPackageLocation(TestCaseWithFactory): | |||
602 | 214 | self.assertEqual( | 224 | self.assertEqual( |
603 | 215 | location_ubuntu_hoary, location_ubuntu_hoary_again) | 225 | location_ubuntu_hoary, location_ubuntu_hoary_again) |
604 | 216 | 226 | ||
605 | 227 | def testCompareChannels(self): | ||
606 | 228 | location_ubuntu_hoary = self.getPackageLocation(channel="stable") | ||
607 | 229 | location_ubuntu_hoary_again = self.getPackageLocation(channel="edge") | ||
608 | 230 | self.assertNotEqual(location_ubuntu_hoary, location_ubuntu_hoary_again) | ||
609 | 231 | |||
610 | 232 | location_ubuntu_hoary_again.channel = "stable" | ||
611 | 233 | self.assertEqual(location_ubuntu_hoary, location_ubuntu_hoary_again) | ||
612 | 234 | |||
613 | 217 | def testRepresentation(self): | 235 | def testRepresentation(self): |
614 | 218 | """Check if PackageLocation is represented correctly.""" | 236 | """Check if PackageLocation is represented correctly.""" |
615 | 219 | location_ubuntu_hoary = self.getPackageLocation() | 237 | location_ubuntu_hoary = self.getPackageLocation() |
616 | diff --git a/lib/lp/soyuz/enums.py b/lib/lp/soyuz/enums.py | |||
617 | index d60ea48..9953f43 100644 | |||
618 | --- a/lib/lp/soyuz/enums.py | |||
619 | +++ b/lib/lp/soyuz/enums.py | |||
620 | @@ -208,6 +208,13 @@ class BinaryPackageFileType(DBEnumeratedType): | |||
621 | 208 | build environment. | 208 | build environment. |
622 | 209 | """) | 209 | """) |
623 | 210 | 210 | ||
624 | 211 | WHL = DBItem(6, """ | ||
625 | 212 | Python Wheel | ||
626 | 213 | |||
627 | 214 | The "wheel" binary package format for Python, originally defined in | ||
628 | 215 | U{https://peps.python.org/pep-0427/}. | ||
629 | 216 | """) | ||
630 | 217 | |||
631 | 211 | 218 | ||
632 | 212 | class BinaryPackageFormat(DBEnumeratedType): | 219 | class BinaryPackageFormat(DBEnumeratedType): |
633 | 213 | """Binary Package Format | 220 | """Binary Package Format |
634 | @@ -251,6 +258,13 @@ class BinaryPackageFormat(DBEnumeratedType): | |||
635 | 251 | This is the binary package format used for shipping debug symbols | 258 | This is the binary package format used for shipping debug symbols |
636 | 252 | in Ubuntu and similar distributions.""") | 259 | in Ubuntu and similar distributions.""") |
637 | 253 | 260 | ||
638 | 261 | WHL = DBItem(6, """ | ||
639 | 262 | Python Wheel | ||
640 | 263 | |||
641 | 264 | The "wheel" binary package format for Python, originally defined in | ||
642 | 265 | U{https://peps.python.org/pep-0427/}. | ||
643 | 266 | """) | ||
644 | 267 | |||
645 | 254 | 268 | ||
646 | 255 | class PackageCopyPolicy(DBEnumeratedType): | 269 | class PackageCopyPolicy(DBEnumeratedType): |
647 | 256 | """Package copying policy. | 270 | """Package copying policy. |
648 | diff --git a/lib/lp/soyuz/interfaces/publishing.py b/lib/lp/soyuz/interfaces/publishing.py | |||
649 | index a1cfeab..21a3cac 100644 | |||
650 | --- a/lib/lp/soyuz/interfaces/publishing.py | |||
651 | +++ b/lib/lp/soyuz/interfaces/publishing.py | |||
652 | @@ -50,7 +50,6 @@ from zope.schema import ( | |||
653 | 50 | Date, | 50 | Date, |
654 | 51 | Datetime, | 51 | Datetime, |
655 | 52 | Int, | 52 | Int, |
656 | 53 | List, | ||
657 | 54 | Text, | 53 | Text, |
658 | 55 | TextLine, | 54 | TextLine, |
659 | 56 | ) | 55 | ) |
660 | @@ -269,9 +268,8 @@ class ISourcePackagePublishingHistoryPublic(IPublishingView): | |||
661 | 269 | vocabulary=PackagePublishingPocket, | 268 | vocabulary=PackagePublishingPocket, |
662 | 270 | required=True, readonly=True, | 269 | required=True, readonly=True, |
663 | 271 | )) | 270 | )) |
667 | 272 | channel = List( | 271 | channel = TextLine( |
668 | 273 | value_type=TextLine(), title=_("Channel"), | 272 | title=_("Channel"), required=False, readonly=False, |
666 | 274 | required=False, readonly=False, | ||
669 | 275 | description=_( | 273 | description=_( |
670 | 276 | "The channel into which this entry is published " | 274 | "The channel into which this entry is published " |
671 | 277 | "(only for archives published using Artifactory)")) | 275 | "(only for archives published using Artifactory)")) |
672 | @@ -700,9 +698,8 @@ class IBinaryPackagePublishingHistoryPublic(IPublishingView): | |||
673 | 700 | vocabulary=PackagePublishingPocket, | 698 | vocabulary=PackagePublishingPocket, |
674 | 701 | required=True, readonly=True, | 699 | required=True, readonly=True, |
675 | 702 | )) | 700 | )) |
679 | 703 | channel = List( | 701 | channel = TextLine( |
680 | 704 | value_type=TextLine(), title=_("Channel"), | 702 | title=_("Channel"), required=False, readonly=False, |
678 | 705 | required=False, readonly=False, | ||
681 | 706 | description=_( | 703 | description=_( |
682 | 707 | "The channel into which this entry is published " | 704 | "The channel into which this entry is published " |
683 | 708 | "(only for archives published using Artifactory)")) | 705 | "(only for archives published using Artifactory)")) |
684 | @@ -970,7 +967,8 @@ class IPublishingSet(Interface): | |||
685 | 970 | def newSourcePublication(archive, sourcepackagerelease, distroseries, | 967 | def newSourcePublication(archive, sourcepackagerelease, distroseries, |
686 | 971 | component, section, pocket, ancestor, | 968 | component, section, pocket, ancestor, |
687 | 972 | create_dsd_job=True, copied_from_archive=None, | 969 | create_dsd_job=True, copied_from_archive=None, |
689 | 973 | creator=None, sponsor=None, packageupload=None): | 970 | creator=None, sponsor=None, packageupload=None, |
690 | 971 | channel=None): | ||
691 | 974 | """Create a new `SourcePackagePublishingHistory`. | 972 | """Create a new `SourcePackagePublishingHistory`. |
692 | 975 | 973 | ||
693 | 976 | :param archive: An `IArchive` | 974 | :param archive: An `IArchive` |
694 | diff --git a/lib/lp/soyuz/model/binarypackagerelease.py b/lib/lp/soyuz/model/binarypackagerelease.py | |||
695 | index 25cae48..7134895 100644 | |||
696 | --- a/lib/lp/soyuz/model/binarypackagerelease.py | |||
697 | +++ b/lib/lp/soyuz/model/binarypackagerelease.py | |||
698 | @@ -157,6 +157,8 @@ class BinaryPackageRelease(SQLBase): | |||
699 | 157 | determined_filetype = BinaryPackageFileType.UDEB | 157 | determined_filetype = BinaryPackageFileType.UDEB |
700 | 158 | elif file.filename.endswith(".ddeb"): | 158 | elif file.filename.endswith(".ddeb"): |
701 | 159 | determined_filetype = BinaryPackageFileType.DDEB | 159 | determined_filetype = BinaryPackageFileType.DDEB |
702 | 160 | elif file.filename.endswith(".whl"): | ||
703 | 161 | determined_filetype = BinaryPackageFileType.WHL | ||
704 | 160 | else: | 162 | else: |
705 | 161 | raise AssertionError( | 163 | raise AssertionError( |
706 | 162 | 'Unsupported file type: %s' % file.filename) | 164 | 'Unsupported file type: %s' % file.filename) |
707 | diff --git a/lib/lp/soyuz/model/publishing.py b/lib/lp/soyuz/model/publishing.py | |||
708 | index cdab414..8a397b6 100644 | |||
709 | --- a/lib/lp/soyuz/model/publishing.py | |||
710 | +++ b/lib/lp/soyuz/model/publishing.py | |||
711 | @@ -12,6 +12,7 @@ __all__ = [ | |||
712 | 12 | 12 | ||
713 | 13 | from collections import defaultdict | 13 | from collections import defaultdict |
714 | 14 | from datetime import datetime | 14 | from datetime import datetime |
715 | 15 | import json | ||
716 | 15 | from operator import ( | 16 | from operator import ( |
717 | 16 | attrgetter, | 17 | attrgetter, |
718 | 17 | itemgetter, | 18 | itemgetter, |
719 | @@ -20,6 +21,7 @@ from pathlib import Path | |||
720 | 20 | import sys | 21 | import sys |
721 | 21 | 22 | ||
722 | 22 | import pytz | 23 | import pytz |
723 | 24 | from storm.databases.postgres import JSON | ||
724 | 23 | from storm.expr import ( | 25 | from storm.expr import ( |
725 | 24 | And, | 26 | And, |
726 | 25 | Cast, | 27 | Cast, |
727 | @@ -31,10 +33,6 @@ from storm.expr import ( | |||
728 | 31 | Sum, | 33 | Sum, |
729 | 32 | ) | 34 | ) |
730 | 33 | from storm.info import ClassAlias | 35 | from storm.info import ClassAlias |
731 | 34 | from storm.properties import ( | ||
732 | 35 | List, | ||
733 | 36 | Unicode, | ||
734 | 37 | ) | ||
735 | 38 | from storm.store import Store | 36 | from storm.store import Store |
736 | 39 | from storm.zope import IResultSet | 37 | from storm.zope import IResultSet |
737 | 40 | from storm.zope.interfaces import ISQLObjectResultSet | 38 | from storm.zope.interfaces import ISQLObjectResultSet |
738 | @@ -51,6 +49,10 @@ from lp.registry.interfaces.person import validate_public_person | |||
739 | 51 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 49 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
740 | 52 | from lp.registry.interfaces.sourcepackage import SourcePackageType | 50 | from lp.registry.interfaces.sourcepackage import SourcePackageType |
741 | 53 | from lp.registry.model.sourcepackagename import SourcePackageName | 51 | from lp.registry.model.sourcepackagename import SourcePackageName |
742 | 52 | from lp.services.channels import ( | ||
743 | 53 | channel_list_to_string, | ||
744 | 54 | channel_string_to_list, | ||
745 | 55 | ) | ||
746 | 54 | from lp.services.database import bulk | 56 | from lp.services.database import bulk |
747 | 55 | from lp.services.database.constants import UTC_NOW | 57 | from lp.services.database.constants import UTC_NOW |
748 | 56 | from lp.services.database.datetimecol import UtcDateTimeCol | 58 | from lp.services.database.datetimecol import UtcDateTimeCol |
749 | @@ -267,7 +269,7 @@ class SourcePackagePublishingHistory(SQLBase, ArchivePublisherBase): | |||
750 | 267 | pocket = DBEnum(name='pocket', enum=PackagePublishingPocket, | 269 | pocket = DBEnum(name='pocket', enum=PackagePublishingPocket, |
751 | 268 | default=PackagePublishingPocket.RELEASE, | 270 | default=PackagePublishingPocket.RELEASE, |
752 | 269 | allow_none=False) | 271 | allow_none=False) |
754 | 270 | channel = List(name="channel", type=Unicode(), allow_none=True) | 272 | _channel = JSON(name="channel", allow_none=True) |
755 | 271 | archive = ForeignKey(dbName="archive", foreignKey="Archive", notNull=True) | 273 | archive = ForeignKey(dbName="archive", foreignKey="Archive", notNull=True) |
756 | 272 | copied_from_archive = ForeignKey( | 274 | copied_from_archive = ForeignKey( |
757 | 273 | dbName="copied_from_archive", foreignKey="Archive", notNull=False) | 275 | dbName="copied_from_archive", foreignKey="Archive", notNull=False) |
758 | @@ -315,6 +317,13 @@ class SourcePackagePublishingHistory(SQLBase, ArchivePublisherBase): | |||
759 | 315 | self.distroseries.setNewerDistroSeriesVersions([self]) | 317 | self.distroseries.setNewerDistroSeriesVersions([self]) |
760 | 316 | return get_property_cache(self).newer_distroseries_version | 318 | return get_property_cache(self).newer_distroseries_version |
761 | 317 | 319 | ||
762 | 320 | @property | ||
763 | 321 | def channel(self): | ||
764 | 322 | """See `ISourcePackagePublishingHistory`.""" | ||
765 | 323 | if self._channel is None: | ||
766 | 324 | return None | ||
767 | 325 | return channel_list_to_string(*self._channel) | ||
768 | 326 | |||
769 | 318 | def getPublishedBinaries(self): | 327 | def getPublishedBinaries(self): |
770 | 319 | """See `ISourcePackagePublishingHistory`.""" | 328 | """See `ISourcePackagePublishingHistory`.""" |
771 | 320 | publishing_set = getUtility(IPublishingSet) | 329 | publishing_set = getUtility(IPublishingSet) |
772 | @@ -538,7 +547,8 @@ class SourcePackagePublishingHistory(SQLBase, ArchivePublisherBase): | |||
773 | 538 | component=new_component, | 547 | component=new_component, |
774 | 539 | section=new_section, | 548 | section=new_section, |
775 | 540 | creator=creator, | 549 | creator=creator, |
777 | 541 | archive=self.archive) | 550 | archive=self.archive, |
778 | 551 | channel=self.channel) | ||
779 | 542 | 552 | ||
780 | 543 | def copyTo(self, distroseries, pocket, archive, override=None, | 553 | def copyTo(self, distroseries, pocket, archive, override=None, |
781 | 544 | create_dsd_job=True, creator=None, sponsor=None, | 554 | create_dsd_job=True, creator=None, sponsor=None, |
782 | @@ -564,7 +574,8 @@ class SourcePackagePublishingHistory(SQLBase, ArchivePublisherBase): | |||
783 | 564 | creator=creator, | 574 | creator=creator, |
784 | 565 | sponsor=sponsor, | 575 | sponsor=sponsor, |
785 | 566 | copied_from_archive=self.archive, | 576 | copied_from_archive=self.archive, |
787 | 567 | packageupload=packageupload) | 577 | packageupload=packageupload, |
788 | 578 | channel=self.channel) | ||
789 | 568 | 579 | ||
790 | 569 | def getStatusSummaryForBuilds(self): | 580 | def getStatusSummaryForBuilds(self): |
791 | 570 | """See `ISourcePackagePublishingHistory`.""" | 581 | """See `ISourcePackagePublishingHistory`.""" |
792 | @@ -685,7 +696,7 @@ class BinaryPackagePublishingHistory(SQLBase, ArchivePublisherBase): | |||
793 | 685 | datemadepending = UtcDateTimeCol(default=None) | 696 | datemadepending = UtcDateTimeCol(default=None) |
794 | 686 | dateremoved = UtcDateTimeCol(default=None) | 697 | dateremoved = UtcDateTimeCol(default=None) |
795 | 687 | pocket = DBEnum(name='pocket', enum=PackagePublishingPocket) | 698 | pocket = DBEnum(name='pocket', enum=PackagePublishingPocket) |
797 | 688 | channel = List(name="channel", type=Unicode(), allow_none=True) | 699 | _channel = JSON(name="channel", allow_none=True) |
798 | 689 | archive = ForeignKey(dbName="archive", foreignKey="Archive", notNull=True) | 700 | archive = ForeignKey(dbName="archive", foreignKey="Archive", notNull=True) |
799 | 690 | copied_from_archive = ForeignKey( | 701 | copied_from_archive = ForeignKey( |
800 | 691 | dbName="copied_from_archive", foreignKey="Archive", notNull=False) | 702 | dbName="copied_from_archive", foreignKey="Archive", notNull=False) |
801 | @@ -779,6 +790,13 @@ class BinaryPackagePublishingHistory(SQLBase, ArchivePublisherBase): | |||
802 | 779 | distroseries.name, | 790 | distroseries.name, |
803 | 780 | self.distroarchseries.architecturetag) | 791 | self.distroarchseries.architecturetag) |
804 | 781 | 792 | ||
805 | 793 | @property | ||
806 | 794 | def channel(self): | ||
807 | 795 | """See `ISourcePackagePublishingHistory`.""" | ||
808 | 796 | if self._channel is None: | ||
809 | 797 | return None | ||
810 | 798 | return channel_list_to_string(*self._channel) | ||
811 | 799 | |||
812 | 782 | def getDownloadCount(self): | 800 | def getDownloadCount(self): |
813 | 783 | """See `IBinaryPackagePublishingHistory`.""" | 801 | """See `IBinaryPackagePublishingHistory`.""" |
814 | 784 | return self.archive.getPackageDownloadTotal(self.binarypackagerelease) | 802 | return self.archive.getPackageDownloadTotal(self.binarypackagerelease) |
815 | @@ -836,21 +854,26 @@ class BinaryPackagePublishingHistory(SQLBase, ArchivePublisherBase): | |||
816 | 836 | dominant.distroarchseries.architecturetag)) | 854 | dominant.distroarchseries.architecturetag)) |
817 | 837 | 855 | ||
818 | 838 | dominant_build = dominant.binarypackagerelease.build | 856 | dominant_build = dominant.binarypackagerelease.build |
834 | 839 | distroarchseries = dominant_build.distro_arch_series | 857 | # XXX cjwatson 2022-05-01: We can't currently dominate with CI |
835 | 840 | if logger is not None: | 858 | # builds, since supersededby is a reference to a BPB. Just |
836 | 841 | logger.debug( | 859 | # leave supersededby unset in that case for now, which isn't |
837 | 842 | "The %s build of %s has been judged as superseded by the " | 860 | # ideal but will work well enough. |
838 | 843 | "build of %s. Arch-specific == %s" % ( | 861 | if dominant_build is not None: |
839 | 844 | distroarchseries.architecturetag, | 862 | distroarchseries = dominant_build.distro_arch_series |
840 | 845 | self.binarypackagerelease.title, | 863 | if logger is not None: |
841 | 846 | dominant_build.source_package_release.title, | 864 | logger.debug( |
842 | 847 | self.binarypackagerelease.architecturespecific)) | 865 | "The %s build of %s has been judged as superseded by " |
843 | 848 | # Binary package releases are superseded by the new build, | 866 | "the build of %s. Arch-specific == %s" % ( |
844 | 849 | # not the new binary package release. This is because | 867 | distroarchseries.architecturetag, |
845 | 850 | # there may not *be* a new matching binary package - | 868 | self.binarypackagerelease.title, |
846 | 851 | # source packages can change the binaries they build | 869 | dominant_build.source_package_release.title, |
847 | 852 | # between releases. | 870 | self.binarypackagerelease.architecturespecific)) |
848 | 853 | self.supersededby = dominant_build | 871 | # Binary package releases are superseded by the new build, |
849 | 872 | # not the new binary package release. This is because | ||
850 | 873 | # there may not *be* a new matching binary package - | ||
851 | 874 | # source packages can change the binaries they build | ||
852 | 875 | # between releases. | ||
853 | 876 | self.supersededby = dominant_build | ||
854 | 854 | 877 | ||
855 | 855 | debug = getUtility(IPublishingSet).findCorrespondingDDEBPublications( | 878 | debug = getUtility(IPublishingSet).findCorrespondingDDEBPublications( |
856 | 856 | [self]) | 879 | [self]) |
857 | @@ -941,7 +964,8 @@ class BinaryPackagePublishingHistory(SQLBase, ArchivePublisherBase): | |||
858 | 941 | priority=new_priority, | 964 | priority=new_priority, |
859 | 942 | creator=creator, | 965 | creator=creator, |
860 | 943 | archive=debug.archive, | 966 | archive=debug.archive, |
862 | 944 | phased_update_percentage=new_phased_update_percentage) | 967 | phased_update_percentage=new_phased_update_percentage, |
863 | 968 | _channel=removeSecurityProxy(debug)._channel) | ||
864 | 945 | 969 | ||
865 | 946 | # Append the modified package publishing entry | 970 | # Append the modified package publishing entry |
866 | 947 | return BinaryPackagePublishingHistory( | 971 | return BinaryPackagePublishingHistory( |
867 | @@ -957,7 +981,8 @@ class BinaryPackagePublishingHistory(SQLBase, ArchivePublisherBase): | |||
868 | 957 | priority=new_priority, | 981 | priority=new_priority, |
869 | 958 | archive=self.archive, | 982 | archive=self.archive, |
870 | 959 | creator=creator, | 983 | creator=creator, |
872 | 960 | phased_update_percentage=new_phased_update_percentage) | 984 | phased_update_percentage=new_phased_update_percentage, |
873 | 985 | _channel=self._channel) | ||
874 | 961 | 986 | ||
875 | 962 | def copyTo(self, distroseries, pocket, archive): | 987 | def copyTo(self, distroseries, pocket, archive): |
876 | 963 | """See `BinaryPackagePublishingHistory`.""" | 988 | """See `BinaryPackagePublishingHistory`.""" |
877 | @@ -1086,10 +1111,15 @@ class PublishingSet: | |||
878 | 1086 | """Utilities for manipulating publications in batches.""" | 1111 | """Utilities for manipulating publications in batches.""" |
879 | 1087 | 1112 | ||
880 | 1088 | def publishBinaries(self, archive, distroseries, pocket, binaries, | 1113 | def publishBinaries(self, archive, distroseries, pocket, binaries, |
882 | 1089 | copied_from_archives=None): | 1114 | copied_from_archives=None, channel=None): |
883 | 1090 | """See `IPublishingSet`.""" | 1115 | """See `IPublishingSet`.""" |
884 | 1091 | if copied_from_archives is None: | 1116 | if copied_from_archives is None: |
885 | 1092 | copied_from_archives = {} | 1117 | copied_from_archives = {} |
886 | 1118 | if channel is not None: | ||
887 | 1119 | if pocket != PackagePublishingPocket.RELEASE: | ||
888 | 1120 | raise AssertionError( | ||
889 | 1121 | "Channel publications must be in the RELEASE pocket") | ||
890 | 1122 | channel = channel_string_to_list(channel) | ||
891 | 1093 | # Expand the dict of binaries into a list of tuples including the | 1123 | # Expand the dict of binaries into a list of tuples including the |
892 | 1094 | # architecture. | 1124 | # architecture. |
893 | 1095 | if distroseries.distribution != archive.distribution: | 1125 | if distroseries.distribution != archive.distribution: |
894 | @@ -1124,6 +1154,9 @@ class PublishingSet: | |||
895 | 1124 | BinaryPackageRelease.binarypackagenameID, | 1154 | BinaryPackageRelease.binarypackagenameID, |
896 | 1125 | BinaryPackageRelease.version), | 1155 | BinaryPackageRelease.version), |
897 | 1126 | BinaryPackagePublishingHistory.pocket == pocket, | 1156 | BinaryPackagePublishingHistory.pocket == pocket, |
898 | 1157 | Not(IsDistinctFrom( | ||
899 | 1158 | BinaryPackagePublishingHistory._channel, | ||
900 | 1159 | json.dumps(channel) if channel is not None else None)), | ||
901 | 1127 | BinaryPackagePublishingHistory.status.is_in( | 1160 | BinaryPackagePublishingHistory.status.is_in( |
902 | 1128 | active_publishing_status), | 1161 | active_publishing_status), |
903 | 1129 | BinaryPackageRelease.id == | 1162 | BinaryPackageRelease.id == |
904 | @@ -1141,12 +1174,13 @@ class PublishingSet: | |||
905 | 1141 | BPPH = BinaryPackagePublishingHistory | 1174 | BPPH = BinaryPackagePublishingHistory |
906 | 1142 | return bulk.create( | 1175 | return bulk.create( |
907 | 1143 | (BPPH.archive, BPPH.copied_from_archive, | 1176 | (BPPH.archive, BPPH.copied_from_archive, |
909 | 1144 | BPPH.distroarchseries, BPPH.pocket, | 1177 | BPPH.distroarchseries, BPPH.pocket, BPPH._channel, |
910 | 1145 | BPPH.binarypackagerelease, BPPH.binarypackagename, | 1178 | BPPH.binarypackagerelease, BPPH.binarypackagename, |
911 | 1179 | BPPH._binarypackageformat, | ||
912 | 1146 | BPPH.component, BPPH.section, BPPH.priority, | 1180 | BPPH.component, BPPH.section, BPPH.priority, |
913 | 1147 | BPPH.phased_update_percentage, BPPH.status, BPPH.datecreated), | 1181 | BPPH.phased_update_percentage, BPPH.status, BPPH.datecreated), |
916 | 1148 | [(archive, copied_from_archives.get(bpr), das, pocket, bpr, | 1182 | [(archive, copied_from_archives.get(bpr), das, pocket, channel, |
917 | 1149 | bpr.binarypackagename, | 1183 | bpr, bpr.binarypackagename, bpr.binpackageformat, |
918 | 1150 | get_component(archive, das.distroseries, component), | 1184 | get_component(archive, das.distroseries, component), |
919 | 1151 | section, priority, phased_update_percentage, | 1185 | section, priority, phased_update_percentage, |
920 | 1152 | PackagePublishingStatus.PENDING, UTC_NOW) | 1186 | PackagePublishingStatus.PENDING, UTC_NOW) |
921 | @@ -1156,7 +1190,7 @@ class PublishingSet: | |||
922 | 1156 | get_objects=True) | 1190 | get_objects=True) |
923 | 1157 | 1191 | ||
924 | 1158 | def copyBinaries(self, archive, distroseries, pocket, bpphs, policy=None, | 1192 | def copyBinaries(self, archive, distroseries, pocket, bpphs, policy=None, |
926 | 1159 | source_override=None): | 1193 | source_override=None, channel=None): |
927 | 1160 | """See `IPublishingSet`.""" | 1194 | """See `IPublishingSet`.""" |
928 | 1161 | from lp.soyuz.adapters.overrides import BinaryOverride | 1195 | from lp.soyuz.adapters.overrides import BinaryOverride |
929 | 1162 | if distroseries.distribution != archive.distribution: | 1196 | if distroseries.distribution != archive.distribution: |
930 | @@ -1228,13 +1262,14 @@ class PublishingSet: | |||
931 | 1228 | bpph.binarypackagerelease: bpph.archive for bpph in bpphs} | 1262 | bpph.binarypackagerelease: bpph.archive for bpph in bpphs} |
932 | 1229 | return self.publishBinaries( | 1263 | return self.publishBinaries( |
933 | 1230 | archive, distroseries, pocket, with_overrides, | 1264 | archive, distroseries, pocket, with_overrides, |
935 | 1231 | copied_from_archives) | 1265 | copied_from_archives, channel=channel) |
936 | 1232 | 1266 | ||
937 | 1233 | def newSourcePublication(self, archive, sourcepackagerelease, | 1267 | def newSourcePublication(self, archive, sourcepackagerelease, |
938 | 1234 | distroseries, component, section, pocket, | 1268 | distroseries, component, section, pocket, |
939 | 1235 | ancestor=None, create_dsd_job=True, | 1269 | ancestor=None, create_dsd_job=True, |
940 | 1236 | copied_from_archive=None, | 1270 | copied_from_archive=None, |
942 | 1237 | creator=None, sponsor=None, packageupload=None): | 1271 | creator=None, sponsor=None, packageupload=None, |
943 | 1272 | channel=None): | ||
944 | 1238 | """See `IPublishingSet`.""" | 1273 | """See `IPublishingSet`.""" |
945 | 1239 | # Circular import. | 1274 | # Circular import. |
946 | 1240 | from lp.registry.model.distributionsourcepackage import ( | 1275 | from lp.registry.model.distributionsourcepackage import ( |
947 | @@ -1246,6 +1281,15 @@ class PublishingSet: | |||
948 | 1246 | "Series distribution %s doesn't match archive distribution %s." | 1281 | "Series distribution %s doesn't match archive distribution %s." |
949 | 1247 | % (distroseries.distribution.name, archive.distribution.name)) | 1282 | % (distroseries.distribution.name, archive.distribution.name)) |
950 | 1248 | 1283 | ||
951 | 1284 | if channel is not None: | ||
952 | 1285 | if sourcepackagerelease.format == SourcePackageType.DPKG: | ||
953 | 1286 | raise AssertionError( | ||
954 | 1287 | "Can't publish dpkg source packages to a channel") | ||
955 | 1288 | if pocket != PackagePublishingPocket.RELEASE: | ||
956 | 1289 | raise AssertionError( | ||
957 | 1290 | "Channel publications must be in the RELEASE pocket") | ||
958 | 1291 | channel = channel_string_to_list(channel) | ||
959 | 1292 | |||
960 | 1249 | pub = SourcePackagePublishingHistory( | 1293 | pub = SourcePackagePublishingHistory( |
961 | 1250 | distroseries=distroseries, | 1294 | distroseries=distroseries, |
962 | 1251 | pocket=pocket, | 1295 | pocket=pocket, |
963 | @@ -1261,7 +1305,8 @@ class PublishingSet: | |||
964 | 1261 | ancestor=ancestor, | 1305 | ancestor=ancestor, |
965 | 1262 | creator=creator, | 1306 | creator=creator, |
966 | 1263 | sponsor=sponsor, | 1307 | sponsor=sponsor, |
968 | 1264 | packageupload=packageupload) | 1308 | packageupload=packageupload, |
969 | 1309 | _channel=channel) | ||
970 | 1265 | DistributionSourcePackage.ensure(pub) | 1310 | DistributionSourcePackage.ensure(pub) |
971 | 1266 | 1311 | ||
972 | 1267 | if create_dsd_job and archive == distroseries.main_archive: | 1312 | if create_dsd_job and archive == distroseries.main_archive: |
973 | diff --git a/lib/lp/soyuz/tests/test_publishing.py b/lib/lp/soyuz/tests/test_publishing.py | |||
974 | index 41cdf25..7d91f43 100644 | |||
975 | --- a/lib/lp/soyuz/tests/test_publishing.py | |||
976 | +++ b/lib/lp/soyuz/tests/test_publishing.py | |||
977 | @@ -36,6 +36,7 @@ from lp.registry.interfaces.sourcepackage import ( | |||
978 | 36 | SourcePackageUrgency, | 36 | SourcePackageUrgency, |
979 | 37 | ) | 37 | ) |
980 | 38 | from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet | 38 | from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet |
981 | 39 | from lp.services.channels import channel_string_to_list | ||
982 | 39 | from lp.services.config import config | 40 | from lp.services.config import config |
983 | 40 | from lp.services.database.constants import UTC_NOW | 41 | from lp.services.database.constants import UTC_NOW |
984 | 41 | from lp.services.librarian.interfaces import ILibraryFileAliasSet | 42 | from lp.services.librarian.interfaces import ILibraryFileAliasSet |
985 | @@ -212,7 +213,8 @@ class SoyuzTestPublisher: | |||
986 | 212 | build_conflicts_indep=None, | 213 | build_conflicts_indep=None, |
987 | 213 | dsc_maintainer_rfc822='Foo Bar <foo@bar.com>', | 214 | dsc_maintainer_rfc822='Foo Bar <foo@bar.com>', |
988 | 214 | maintainer=None, creator=None, date_uploaded=UTC_NOW, | 215 | maintainer=None, creator=None, date_uploaded=UTC_NOW, |
990 | 215 | spr_only=False, user_defined_fields=None): | 216 | spr_only=False, user_defined_fields=None, |
991 | 217 | format=SourcePackageType.DPKG, channel=None): | ||
992 | 216 | """Return a mock source publishing record. | 218 | """Return a mock source publishing record. |
993 | 217 | 219 | ||
994 | 218 | if spr_only is specified, the source is not published and the | 220 | if spr_only is specified, the source is not published and the |
995 | @@ -238,7 +240,7 @@ class SoyuzTestPublisher: | |||
996 | 238 | 240 | ||
997 | 239 | spr = distroseries.createUploadedSourcePackageRelease( | 241 | spr = distroseries.createUploadedSourcePackageRelease( |
998 | 240 | sourcepackagename=spn, | 242 | sourcepackagename=spn, |
1000 | 241 | format=SourcePackageType.DPKG, | 243 | format=format, |
1001 | 242 | maintainer=maintainer, | 244 | maintainer=maintainer, |
1002 | 243 | creator=creator, | 245 | creator=creator, |
1003 | 244 | component=component, | 246 | component=component, |
1004 | @@ -289,6 +291,8 @@ class SoyuzTestPublisher: | |||
1005 | 289 | datepublished = UTC_NOW | 291 | datepublished = UTC_NOW |
1006 | 290 | else: | 292 | else: |
1007 | 291 | datepublished = None | 293 | datepublished = None |
1008 | 294 | if channel is not None: | ||
1009 | 295 | channel = channel_string_to_list(channel) | ||
1010 | 292 | 296 | ||
1011 | 293 | spph = SourcePackagePublishingHistory( | 297 | spph = SourcePackagePublishingHistory( |
1012 | 294 | distroseries=distroseries, | 298 | distroseries=distroseries, |
1013 | @@ -304,7 +308,8 @@ class SoyuzTestPublisher: | |||
1014 | 304 | scheduleddeletiondate=scheduleddeletiondate, | 308 | scheduleddeletiondate=scheduleddeletiondate, |
1015 | 305 | pocket=pocket, | 309 | pocket=pocket, |
1016 | 306 | archive=archive, | 310 | archive=archive, |
1018 | 307 | creator=creator) | 311 | creator=creator, |
1019 | 312 | _channel=channel) | ||
1020 | 308 | 313 | ||
1021 | 309 | return spph | 314 | return spph |
1022 | 310 | 315 | ||
1023 | @@ -328,7 +333,8 @@ class SoyuzTestPublisher: | |||
1024 | 328 | builder=None, | 333 | builder=None, |
1025 | 329 | component='main', | 334 | component='main', |
1026 | 330 | phased_update_percentage=None, | 335 | phased_update_percentage=None, |
1028 | 331 | with_debug=False, user_defined_fields=None): | 336 | with_debug=False, user_defined_fields=None, |
1029 | 337 | channel=None): | ||
1030 | 332 | """Return a list of binary publishing records.""" | 338 | """Return a list of binary publishing records.""" |
1031 | 333 | if distroseries is None: | 339 | if distroseries is None: |
1032 | 334 | distroseries = self.distroseries | 340 | distroseries = self.distroseries |
1033 | @@ -366,7 +372,7 @@ class SoyuzTestPublisher: | |||
1034 | 366 | pub_binaries += self.publishBinaryInArchive( | 372 | pub_binaries += self.publishBinaryInArchive( |
1035 | 367 | binarypackagerelease_ddeb, archive, status, | 373 | binarypackagerelease_ddeb, archive, status, |
1036 | 368 | pocket, scheduleddeletiondate, dateremoved, | 374 | pocket, scheduleddeletiondate, dateremoved, |
1038 | 369 | phased_update_percentage) | 375 | phased_update_percentage, channel=channel) |
1039 | 370 | else: | 376 | else: |
1040 | 371 | binarypackagerelease_ddeb = None | 377 | binarypackagerelease_ddeb = None |
1041 | 372 | 378 | ||
1042 | @@ -378,7 +384,8 @@ class SoyuzTestPublisher: | |||
1043 | 378 | user_defined_fields=user_defined_fields) | 384 | user_defined_fields=user_defined_fields) |
1044 | 379 | pub_binaries += self.publishBinaryInArchive( | 385 | pub_binaries += self.publishBinaryInArchive( |
1045 | 380 | binarypackagerelease, archive, status, pocket, | 386 | binarypackagerelease, archive, status, pocket, |
1047 | 381 | scheduleddeletiondate, dateremoved, phased_update_percentage) | 387 | scheduleddeletiondate, dateremoved, phased_update_percentage, |
1048 | 388 | channel=channel) | ||
1049 | 382 | published_binaries.extend(pub_binaries) | 389 | published_binaries.extend(pub_binaries) |
1050 | 383 | package_upload = self.addPackageUpload( | 390 | package_upload = self.addPackageUpload( |
1051 | 384 | archive, distroseries, pocket, | 391 | archive, distroseries, pocket, |
1052 | @@ -476,7 +483,7 @@ class SoyuzTestPublisher: | |||
1053 | 476 | status=PackagePublishingStatus.PENDING, | 483 | status=PackagePublishingStatus.PENDING, |
1054 | 477 | pocket=PackagePublishingPocket.RELEASE, | 484 | pocket=PackagePublishingPocket.RELEASE, |
1055 | 478 | scheduleddeletiondate=None, dateremoved=None, | 485 | scheduleddeletiondate=None, dateremoved=None, |
1057 | 479 | phased_update_percentage=None): | 486 | phased_update_percentage=None, channel=None): |
1058 | 480 | """Return the corresponding BinaryPackagePublishingHistory.""" | 487 | """Return the corresponding BinaryPackagePublishingHistory.""" |
1059 | 481 | distroarchseries = binarypackagerelease.build.distro_arch_series | 488 | distroarchseries = binarypackagerelease.build.distro_arch_series |
1060 | 482 | 489 | ||
1061 | @@ -485,6 +492,8 @@ class SoyuzTestPublisher: | |||
1062 | 485 | archs = [distroarchseries] | 492 | archs = [distroarchseries] |
1063 | 486 | else: | 493 | else: |
1064 | 487 | archs = distroarchseries.distroseries.architectures | 494 | archs = distroarchseries.distroseries.architectures |
1065 | 495 | if channel is not None: | ||
1066 | 496 | channel = channel_string_to_list(channel) | ||
1067 | 488 | 497 | ||
1068 | 489 | pub_binaries = [] | 498 | pub_binaries = [] |
1069 | 490 | for arch in archs: | 499 | for arch in archs: |
1070 | @@ -502,7 +511,8 @@ class SoyuzTestPublisher: | |||
1071 | 502 | datecreated=UTC_NOW, | 511 | datecreated=UTC_NOW, |
1072 | 503 | pocket=pocket, | 512 | pocket=pocket, |
1073 | 504 | archive=archive, | 513 | archive=archive, |
1075 | 505 | phased_update_percentage=phased_update_percentage) | 514 | phased_update_percentage=phased_update_percentage, |
1076 | 515 | _channel=channel) | ||
1077 | 506 | if status == PackagePublishingStatus.PUBLISHED: | 516 | if status == PackagePublishingStatus.PUBLISHED: |
1078 | 507 | pub.datepublished = UTC_NOW | 517 | pub.datepublished = UTC_NOW |
1079 | 508 | pub_binaries.append(pub) | 518 | pub_binaries.append(pub) |
1080 | @@ -1583,19 +1593,24 @@ class TestPublishBinaries(TestCaseWithFactory): | |||
1081 | 1583 | 1593 | ||
1082 | 1584 | layer = LaunchpadZopelessLayer | 1594 | layer = LaunchpadZopelessLayer |
1083 | 1585 | 1595 | ||
1085 | 1586 | def makeArgs(self, bprs, distroseries, archive=None): | 1596 | def makeArgs(self, bprs, distroseries, archive=None, channel=None): |
1086 | 1587 | """Create a dict of arguments for publishBinaries.""" | 1597 | """Create a dict of arguments for publishBinaries.""" |
1087 | 1588 | if archive is None: | 1598 | if archive is None: |
1088 | 1589 | archive = distroseries.main_archive | 1599 | archive = distroseries.main_archive |
1090 | 1590 | return { | 1600 | args = { |
1091 | 1591 | 'archive': archive, | 1601 | 'archive': archive, |
1092 | 1592 | 'distroseries': distroseries, | 1602 | 'distroseries': distroseries, |
1094 | 1593 | 'pocket': PackagePublishingPocket.BACKPORTS, | 1603 | 'pocket': ( |
1095 | 1604 | PackagePublishingPocket.BACKPORTS if channel is None | ||
1096 | 1605 | else PackagePublishingPocket.RELEASE), | ||
1097 | 1594 | 'binaries': { | 1606 | 'binaries': { |
1098 | 1595 | bpr: (self.factory.makeComponent(), | 1607 | bpr: (self.factory.makeComponent(), |
1099 | 1596 | self.factory.makeSection(), | 1608 | self.factory.makeSection(), |
1100 | 1597 | PackagePublishingPriority.REQUIRED, 50) for bpr in bprs}, | 1609 | PackagePublishingPriority.REQUIRED, 50) for bpr in bprs}, |
1101 | 1598 | } | 1610 | } |
1102 | 1611 | if channel is not None: | ||
1103 | 1612 | args['channel'] = channel | ||
1104 | 1613 | return args | ||
1105 | 1599 | 1614 | ||
1106 | 1600 | def test_architecture_dependent(self): | 1615 | def test_architecture_dependent(self): |
1107 | 1601 | # Architecture-dependent binaries get created as PENDING in the | 1616 | # Architecture-dependent binaries get created as PENDING in the |
1108 | @@ -1614,8 +1629,8 @@ class TestPublishBinaries(TestCaseWithFactory): | |||
1109 | 1614 | overrides = args['binaries'][bpr] | 1629 | overrides = args['binaries'][bpr] |
1110 | 1615 | self.assertEqual(bpr, bpph.binarypackagerelease) | 1630 | self.assertEqual(bpr, bpph.binarypackagerelease) |
1111 | 1616 | self.assertEqual( | 1631 | self.assertEqual( |
1114 | 1617 | (args['archive'], target_das, args['pocket']), | 1632 | (args['archive'], target_das, args['pocket'], None), |
1115 | 1618 | (bpph.archive, bpph.distroarchseries, bpph.pocket)) | 1633 | (bpph.archive, bpph.distroarchseries, bpph.pocket, bpph.channel)) |
1116 | 1619 | self.assertEqual( | 1634 | self.assertEqual( |
1117 | 1620 | overrides, | 1635 | overrides, |
1118 | 1621 | (bpph.component, bpph.section, bpph.priority, | 1636 | (bpph.component, bpph.section, bpph.priority, |
1119 | @@ -1670,30 +1685,59 @@ class TestPublishBinaries(TestCaseWithFactory): | |||
1120 | 1670 | args['pocket'] = PackagePublishingPocket.RELEASE | 1685 | args['pocket'] = PackagePublishingPocket.RELEASE |
1121 | 1671 | [another_bpph] = getUtility(IPublishingSet).publishBinaries(**args) | 1686 | [another_bpph] = getUtility(IPublishingSet).publishBinaries(**args) |
1122 | 1672 | 1687 | ||
1123 | 1688 | def test_channel(self): | ||
1124 | 1689 | bpr = self.factory.makeBinaryPackageRelease( | ||
1125 | 1690 | binpackageformat=BinaryPackageFormat.WHL) | ||
1126 | 1691 | target_das = self.factory.makeDistroArchSeries() | ||
1127 | 1692 | args = self.makeArgs([bpr], target_das.distroseries, channel="stable") | ||
1128 | 1693 | [bpph] = getUtility(IPublishingSet).publishBinaries(**args) | ||
1129 | 1694 | self.assertEqual(bpr, bpph.binarypackagerelease) | ||
1130 | 1695 | self.assertEqual( | ||
1131 | 1696 | (args["archive"], target_das, args["pocket"], args["channel"]), | ||
1132 | 1697 | (bpph.archive, bpph.distroarchseries, bpph.pocket, bpph.channel)) | ||
1133 | 1698 | self.assertEqual(PackagePublishingStatus.PENDING, bpph.status) | ||
1134 | 1699 | |||
1135 | 1700 | def test_does_not_duplicate_by_channel(self): | ||
1136 | 1701 | bpr = self.factory.makeBinaryPackageRelease( | ||
1137 | 1702 | binpackageformat=BinaryPackageFormat.WHL) | ||
1138 | 1703 | target_das = self.factory.makeDistroArchSeries() | ||
1139 | 1704 | args = self.makeArgs([bpr], target_das.distroseries, channel="stable") | ||
1140 | 1705 | [bpph] = getUtility(IPublishingSet).publishBinaries(**args) | ||
1141 | 1706 | self.assertContentEqual( | ||
1142 | 1707 | [], getUtility(IPublishingSet).publishBinaries(**args)) | ||
1143 | 1708 | args["channel"] = "edge" | ||
1144 | 1709 | [another_bpph] = getUtility(IPublishingSet).publishBinaries(**args) | ||
1145 | 1710 | |||
1146 | 1673 | 1711 | ||
1147 | 1674 | class TestChangeOverride(TestNativePublishingBase): | 1712 | class TestChangeOverride(TestNativePublishingBase): |
1148 | 1675 | """Test that changing overrides works.""" | 1713 | """Test that changing overrides works.""" |
1149 | 1676 | 1714 | ||
1150 | 1677 | def setUpOverride(self, status=SeriesStatus.DEVELOPMENT, | 1715 | def setUpOverride(self, status=SeriesStatus.DEVELOPMENT, |
1153 | 1678 | pocket=PackagePublishingPocket.RELEASE, binary=False, | 1716 | pocket=PackagePublishingPocket.RELEASE, channel=None, |
1154 | 1679 | ddeb=False, **kwargs): | 1717 | binary=False, format=None, ddeb=False, **kwargs): |
1155 | 1680 | self.distroseries.status = status | 1718 | self.distroseries.status = status |
1156 | 1719 | get_pub_kwargs = {"pocket": pocket, "channel": channel} | ||
1157 | 1720 | if format is not None: | ||
1158 | 1721 | get_pub_kwargs["format"] = format | ||
1159 | 1681 | if ddeb: | 1722 | if ddeb: |
1161 | 1682 | pub = self.getPubBinaries(pocket=pocket, with_debug=True)[2] | 1723 | pub = self.getPubBinaries(with_debug=True, **get_pub_kwargs)[2] |
1162 | 1683 | self.assertEqual( | 1724 | self.assertEqual( |
1163 | 1684 | BinaryPackageFormat.DDEB, | 1725 | BinaryPackageFormat.DDEB, |
1164 | 1685 | pub.binarypackagerelease.binpackageformat) | 1726 | pub.binarypackagerelease.binpackageformat) |
1165 | 1686 | elif binary: | 1727 | elif binary: |
1167 | 1687 | pub = self.getPubBinaries(pocket=pocket)[0] | 1728 | pub = self.getPubBinaries(**get_pub_kwargs)[0] |
1168 | 1688 | else: | 1729 | else: |
1170 | 1689 | pub = self.getPubSource(pocket=pocket) | 1730 | pub = self.getPubSource(**get_pub_kwargs) |
1171 | 1690 | return pub.changeOverride(**kwargs) | 1731 | return pub.changeOverride(**kwargs) |
1172 | 1691 | 1732 | ||
1173 | 1692 | def assertCanOverride(self, status=SeriesStatus.DEVELOPMENT, | 1733 | def assertCanOverride(self, status=SeriesStatus.DEVELOPMENT, |
1176 | 1693 | pocket=PackagePublishingPocket.RELEASE, **kwargs): | 1734 | pocket=PackagePublishingPocket.RELEASE, channel=None, |
1177 | 1694 | new_pub = self.setUpOverride(status=status, pocket=pocket, **kwargs) | 1735 | **kwargs): |
1178 | 1736 | new_pub = self.setUpOverride( | ||
1179 | 1737 | status=status, pocket=pocket, channel=channel, **kwargs) | ||
1180 | 1695 | self.assertEqual(new_pub.status, PackagePublishingStatus.PENDING) | 1738 | self.assertEqual(new_pub.status, PackagePublishingStatus.PENDING) |
1181 | 1696 | self.assertEqual(new_pub.pocket, pocket) | 1739 | self.assertEqual(new_pub.pocket, pocket) |
1182 | 1740 | self.assertEqual(new_pub.channel, channel) | ||
1183 | 1697 | if "new_component" in kwargs: | 1741 | if "new_component" in kwargs: |
1184 | 1698 | self.assertEqual(kwargs["new_component"], new_pub.component.name) | 1742 | self.assertEqual(kwargs["new_component"], new_pub.component.name) |
1185 | 1699 | if "new_section" in kwargs: | 1743 | if "new_section" in kwargs: |
1186 | @@ -1784,6 +1828,12 @@ class TestChangeOverride(TestNativePublishingBase): | |||
1187 | 1784 | self.assertCannotOverride(new_component="partner") | 1828 | self.assertCannotOverride(new_component="partner") |
1188 | 1785 | self.assertCannotOverride(binary=True, new_component="partner") | 1829 | self.assertCannotOverride(binary=True, new_component="partner") |
1189 | 1786 | 1830 | ||
1190 | 1831 | def test_preserves_channel(self): | ||
1191 | 1832 | self.assertCanOverride( | ||
1192 | 1833 | binary=True, format=BinaryPackageFormat.WHL, channel="stable", | ||
1193 | 1834 | new_component="universe", new_section="misc", new_priority="extra", | ||
1194 | 1835 | new_phased_update_percentage=90) | ||
1195 | 1836 | |||
1196 | 1787 | 1837 | ||
1197 | 1788 | class TestPublishingHistoryView(TestCaseWithFactory): | 1838 | class TestPublishingHistoryView(TestCaseWithFactory): |
1198 | 1789 | layer = LaunchpadFunctionalLayer | 1839 | layer = LaunchpadFunctionalLayer |
1199 | diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py | |||
1200 | index 89925b6..29c6059 100644 | |||
1201 | --- a/lib/lp/testing/factory.py | |||
1202 | +++ b/lib/lp/testing/factory.py | |||
1203 | @@ -4029,6 +4029,7 @@ class BareLaunchpadObjectFactory(ObjectFactory): | |||
1204 | 4029 | creator=None, | 4029 | creator=None, |
1205 | 4030 | packageupload=None, | 4030 | packageupload=None, |
1206 | 4031 | spr_creator=None, | 4031 | spr_creator=None, |
1207 | 4032 | channel=None, | ||
1208 | 4032 | **kwargs): | 4033 | **kwargs): |
1209 | 4033 | """Make a `SourcePackagePublishingHistory`. | 4034 | """Make a `SourcePackagePublishingHistory`. |
1210 | 4034 | 4035 | ||
1211 | @@ -4050,6 +4051,7 @@ class BareLaunchpadObjectFactory(ObjectFactory): | |||
1212 | 4050 | is scheduled to be removed. | 4051 | is scheduled to be removed. |
1213 | 4051 | :param ancestor: The publication ancestor parameter. | 4052 | :param ancestor: The publication ancestor parameter. |
1214 | 4052 | :param creator: The publication creator. | 4053 | :param creator: The publication creator. |
1215 | 4054 | :param channel: An optional channel to publish into, as a string. | ||
1216 | 4053 | :param **kwargs: All other parameters are passed through to the | 4055 | :param **kwargs: All other parameters are passed through to the |
1217 | 4054 | makeSourcePackageRelease call if needed. | 4056 | makeSourcePackageRelease call if needed. |
1218 | 4055 | """ | 4057 | """ |
1219 | @@ -4087,7 +4089,7 @@ class BareLaunchpadObjectFactory(ObjectFactory): | |||
1220 | 4087 | archive, sourcepackagerelease, distroseries, | 4089 | archive, sourcepackagerelease, distroseries, |
1221 | 4088 | sourcepackagerelease.component, sourcepackagerelease.section, | 4090 | sourcepackagerelease.component, sourcepackagerelease.section, |
1222 | 4089 | pocket, ancestor=ancestor, creator=creator, | 4091 | pocket, ancestor=ancestor, creator=creator, |
1224 | 4090 | packageupload=packageupload) | 4092 | packageupload=packageupload, channel=channel) |
1225 | 4091 | 4093 | ||
1226 | 4092 | naked_spph = removeSecurityProxy(spph) | 4094 | naked_spph = removeSecurityProxy(spph) |
1227 | 4093 | naked_spph.status = status | 4095 | naked_spph.status = status |
1228 | @@ -4113,7 +4115,7 @@ class BareLaunchpadObjectFactory(ObjectFactory): | |||
1229 | 4113 | version=None, | 4115 | version=None, |
1230 | 4114 | architecturespecific=False, | 4116 | architecturespecific=False, |
1231 | 4115 | with_debug=False, with_file=False, | 4117 | with_debug=False, with_file=False, |
1233 | 4116 | creator=None): | 4118 | creator=None, channel=None): |
1234 | 4117 | """Make a `BinaryPackagePublishingHistory`.""" | 4119 | """Make a `BinaryPackagePublishingHistory`.""" |
1235 | 4118 | if distroarchseries is None: | 4120 | if distroarchseries is None: |
1236 | 4119 | if archive is None: | 4121 | if archive is None: |
1237 | @@ -4144,7 +4146,10 @@ class BareLaunchpadObjectFactory(ObjectFactory): | |||
1238 | 4144 | if priority is None: | 4146 | if priority is None: |
1239 | 4145 | priority = PackagePublishingPriority.OPTIONAL | 4147 | priority = PackagePublishingPriority.OPTIONAL |
1240 | 4146 | if binpackageformat is None: | 4148 | if binpackageformat is None: |
1242 | 4147 | binpackageformat = BinaryPackageFormat.DEB | 4149 | if binarypackagerelease is not None: |
1243 | 4150 | binpackageformat = binarypackagerelease.binpackageformat | ||
1244 | 4151 | else: | ||
1245 | 4152 | binpackageformat = BinaryPackageFormat.DEB | ||
1246 | 4148 | 4153 | ||
1247 | 4149 | if binarypackagerelease is None: | 4154 | if binarypackagerelease is None: |
1248 | 4150 | # Create a new BinaryPackageBuild and BinaryPackageRelease | 4155 | # Create a new BinaryPackageBuild and BinaryPackageRelease |
1249 | @@ -4182,7 +4187,8 @@ class BareLaunchpadObjectFactory(ObjectFactory): | |||
1250 | 4182 | archive, distroarchseries.distroseries, pocket, | 4187 | archive, distroarchseries.distroseries, pocket, |
1251 | 4183 | {binarypackagerelease: ( | 4188 | {binarypackagerelease: ( |
1252 | 4184 | binarypackagerelease.component, binarypackagerelease.section, | 4189 | binarypackagerelease.component, binarypackagerelease.section, |
1254 | 4185 | priority, None)}) | 4190 | priority, None)}, |
1255 | 4191 | channel=channel) | ||
1256 | 4186 | for bpph in bpphs: | 4192 | for bpph in bpphs: |
1257 | 4187 | naked_bpph = removeSecurityProxy(bpph) | 4193 | naked_bpph = removeSecurityProxy(bpph) |
1258 | 4188 | naked_bpph.status = status | 4194 | naked_bpph.status = status |
1259 | @@ -4256,7 +4262,7 @@ class BareLaunchpadObjectFactory(ObjectFactory): | |||
1260 | 4256 | libraryfile=library_file, filetype=filetype)) | 4262 | libraryfile=library_file, filetype=filetype)) |
1261 | 4257 | 4263 | ||
1262 | 4258 | def makeBinaryPackageRelease(self, binarypackagename=None, | 4264 | def makeBinaryPackageRelease(self, binarypackagename=None, |
1264 | 4259 | version=None, build=None, | 4265 | version=None, build=None, ci_build=None, |
1265 | 4260 | binpackageformat=None, component=None, | 4266 | binpackageformat=None, component=None, |
1266 | 4261 | section_name=None, priority=None, | 4267 | section_name=None, priority=None, |
1267 | 4262 | architecturespecific=False, | 4268 | architecturespecific=False, |
1268 | @@ -4270,22 +4276,27 @@ class BareLaunchpadObjectFactory(ObjectFactory): | |||
1269 | 4270 | date_created=None, debug_package=None, | 4276 | date_created=None, debug_package=None, |
1270 | 4271 | homepage=None): | 4277 | homepage=None): |
1271 | 4272 | """Make a `BinaryPackageRelease`.""" | 4278 | """Make a `BinaryPackageRelease`.""" |
1273 | 4273 | if build is None: | 4279 | if build is None and ci_build is None: |
1274 | 4274 | build = self.makeBinaryPackageBuild() | 4280 | build = self.makeBinaryPackageBuild() |
1275 | 4275 | if binarypackagename is None or isinstance(binarypackagename, str): | 4281 | if binarypackagename is None or isinstance(binarypackagename, str): |
1276 | 4276 | binarypackagename = self.getOrMakeBinaryPackageName( | 4282 | binarypackagename = self.getOrMakeBinaryPackageName( |
1277 | 4277 | binarypackagename) | 4283 | binarypackagename) |
1279 | 4278 | if version is None: | 4284 | if version is None and build is not None: |
1280 | 4279 | version = build.source_package_release.version | 4285 | version = build.source_package_release.version |
1281 | 4280 | if binpackageformat is None: | 4286 | if binpackageformat is None: |
1282 | 4281 | binpackageformat = BinaryPackageFormat.DEB | 4287 | binpackageformat = BinaryPackageFormat.DEB |
1284 | 4282 | if component is None: | 4288 | if component is None and build is not None: |
1285 | 4283 | component = build.source_package_release.component | 4289 | component = build.source_package_release.component |
1286 | 4284 | elif isinstance(component, str): | 4290 | elif isinstance(component, str): |
1287 | 4285 | component = getUtility(IComponentSet)[component] | 4291 | component = getUtility(IComponentSet)[component] |
1288 | 4286 | if isinstance(section_name, str): | 4292 | if isinstance(section_name, str): |
1289 | 4287 | section_name = self.makeSection(section_name) | 4293 | section_name = self.makeSection(section_name) |
1291 | 4288 | section = section_name or build.source_package_release.section | 4294 | if section_name is not None: |
1292 | 4295 | section = section_name | ||
1293 | 4296 | elif build is not None: | ||
1294 | 4297 | section = build.source_package_release.section | ||
1295 | 4298 | else: | ||
1296 | 4299 | section = None | ||
1297 | 4289 | if priority is None: | 4300 | if priority is None: |
1298 | 4290 | priority = PackagePublishingPriority.OPTIONAL | 4301 | priority = PackagePublishingPriority.OPTIONAL |
1299 | 4291 | if summary is None: | 4302 | if summary is None: |
1300 | @@ -4294,18 +4305,35 @@ class BareLaunchpadObjectFactory(ObjectFactory): | |||
1301 | 4294 | description = self.getUniqueString("description") | 4305 | description = self.getUniqueString("description") |
1302 | 4295 | if installed_size is None: | 4306 | if installed_size is None: |
1303 | 4296 | installed_size = self.getUniqueInteger() | 4307 | installed_size = self.getUniqueInteger() |
1316 | 4297 | bpr = build.createBinaryPackageRelease( | 4308 | kwargs = { |
1317 | 4298 | binarypackagename=binarypackagename, version=version, | 4309 | "binarypackagename": binarypackagename, |
1318 | 4299 | binpackageformat=binpackageformat, | 4310 | "version": version, |
1319 | 4300 | component=component, section=section, priority=priority, | 4311 | "binpackageformat": binpackageformat, |
1320 | 4301 | summary=summary, description=description, | 4312 | "summary": summary, |
1321 | 4302 | architecturespecific=architecturespecific, | 4313 | "description": description, |
1322 | 4303 | shlibdeps=shlibdeps, depends=depends, recommends=recommends, | 4314 | "architecturespecific": architecturespecific, |
1323 | 4304 | suggests=suggests, conflicts=conflicts, replaces=replaces, | 4315 | "installedsize": installed_size, |
1324 | 4305 | provides=provides, pre_depends=pre_depends, | 4316 | "homepage": homepage, |
1325 | 4306 | enhances=enhances, breaks=breaks, essential=essential, | 4317 | } |
1326 | 4307 | installedsize=installed_size, debug_package=debug_package, | 4318 | if build is not None: |
1327 | 4308 | homepage=homepage) | 4319 | kwargs.update({ |
1328 | 4320 | "component": component, | ||
1329 | 4321 | "section": section, | ||
1330 | 4322 | "priority": priority, | ||
1331 | 4323 | "shlibdeps": shlibdeps, | ||
1332 | 4324 | "depends": depends, | ||
1333 | 4325 | "recommends": recommends, | ||
1334 | 4326 | "suggests": suggests, | ||
1335 | 4327 | "conflicts": conflicts, | ||
1336 | 4328 | "replaces": replaces, | ||
1337 | 4329 | "provides": provides, | ||
1338 | 4330 | "pre_depends": pre_depends, | ||
1339 | 4331 | "enhances": enhances, | ||
1340 | 4332 | "breaks": breaks, | ||
1341 | 4333 | "essential": essential, | ||
1342 | 4334 | "debug_package": debug_package, | ||
1343 | 4335 | }) | ||
1344 | 4336 | bpr = (build or ci_build).createBinaryPackageRelease(**kwargs) | ||
1345 | 4309 | if date_created is not None: | 4337 | if date_created is not None: |
1346 | 4310 | removeSecurityProxy(bpr).datecreated = date_created | 4338 | removeSecurityProxy(bpr).datecreated = date_created |
1347 | 4311 | return bpr | 4339 | return bpr |