87
85
apt_pkg.InitSystem()
90
def _compare_packages_by_version_and_date(get_release, p1, p2):
91
"""Compare publications p1 and p2 by their version; using Debian rules.
93
If the publications are for the same package, compare by datecreated
94
instead. This lets newer records win.
96
if get_release(p1).id == get_release(p2).id:
97
return cmp(p1.datecreated, p2.datecreated)
99
return apt_pkg.VersionCompare(get_release(p1).version,
100
get_release(p2).version)
89
"""Join condition: SourcePackageRelease/SourcePackageName."""
91
SourcePackageName.id == SourcePackageRelease.sourcepackagenameID)
95
"""Join condition: SourcePackageRelease/SourcePackagePublishingHistory.
97
# Avoid circular imports.
98
from lp.soyuz.model.publishing import SourcePackagePublishingHistory
101
SourcePackageRelease.id ==
102
SourcePackagePublishingHistory.sourcepackagereleaseID)
105
class SourcePublicationTraits:
106
"""Basic generalized attributes for `SourcePackagePublishingHistory`.
108
Used by `GeneralizedPublication` to hide the differences from
109
`BinaryPackagePublishingHistory`.
112
def getPackageName(spph):
113
"""Return the name of this publication's source package."""
114
return spph.sourcepackagerelease.sourcepackagename.name
117
def getPackageRelease(spph):
118
"""Return this publication's `SourcePackageRelease`."""
119
return spph.sourcepackagerelease
122
class BinaryPublicationTraits:
123
"""Basic generalized attributes for `BinaryPackagePublishingHistory`.
125
Used by `GeneralizedPublication` to hide the differences from
126
`SourcePackagePublishingHistory`.
129
def getPackageName(bpph):
130
"""Return the name of this publication's binary package."""
131
return bpph.binarypackagerelease.binarypackagename.name
134
def getPackageRelease(bpph):
135
"""Return this publication's `BinaryPackageRelease`."""
136
return bpph.binarypackagerelease
139
class GeneralizedPublication:
140
"""Generalize handling of publication records.
142
This allows us to write code that can be dealing with either
143
`SourcePackagePublishingHistory`s or `BinaryPackagePublishingHistory`s
144
without caring which. Differences are abstracted away in a traits
147
def __init__(self, is_source=True):
149
self.traits = SourcePublicationTraits
151
self.traits = BinaryPublicationTraits
153
def getPackageName(self, pub):
154
"""Get the package's name."""
155
return self.traits.getPackageName(pub)
157
def getPackageVersion(self, pub):
158
"""Obtain the version string for a publicaiton record."""
159
return self.traits.getPackageRelease(pub).version
161
def compare(self, pub1, pub2):
162
"""Compare publications by version.
164
If both publications are for the same version, their creation dates
167
version_comparison = apt_pkg.VersionCompare(
168
self.getPackageVersion(pub1), self.getPackageVersion(pub2))
170
if version_comparison == 0:
171
# Use dates as tie breaker.
172
return cmp(pub1.datecreated, pub2.datecreated)
174
return version_comparison
116
190
self.logger = logger
117
191
self.archive = archive
119
def _dominatePublications(self, pubs):
193
def dominatePackage(self, publications, live_versions, generalization):
194
"""Dominate publications for a single package.
196
The latest publication for any version in `live_versions` stays
197
active. Any older publications (including older publications for
198
live versions with multiple publications) are marked as superseded by
199
the respective oldest live releases that are newer than the superseded
202
Any versions that are newer than anything in `live_versions` are
203
marked as deleted. This should not be possible in Soyuz-native
204
archives, but it can happen during archive imports when the
205
previous latest version of a package has disappeared from the Sources
208
:param publications: Iterable of publications for the same package,
209
in the same archive, series, and pocket, all with status
210
`PackagePublishingStatus.PUBLISHED`.
211
:param live_versions: Iterable of version strings that are still
212
considered live for this package. The given publications will
213
remain active insofar as they represent any of these versions;
214
older publications will be marked as superseded and newer ones
216
:param generalization: A `GeneralizedPublication` helper representing
217
the kind of publications these are--source or binary.
219
# Go through publications from latest version to oldest. This
220
# makes it easy to figure out which release superseded which:
221
# the dominant is always the oldest live release that is newer
222
# than the one being superseded.
223
publications = sorted(
224
publications, cmp=generalization.compare, reverse=True)
226
current_dominant = None
227
dominant_version = None
229
for pub in publications:
230
version = generalization.getPackageVersion(pub)
231
# There should never be two published releases with the same
232
# version. So this comparison is really a string
233
# comparison, not a version comparison: if the versions are
234
# equal by either measure, they're from the same release.
235
if dominant_version is not None and version == dominant_version:
236
# This publication is for a live version, but has been
237
# superseded by a newer publication of the same version.
239
pub.supersede(current_dominant, logger=self.logger)
240
elif version in live_versions:
241
# This publication stays active; if any publications
242
# that follow right after this are to be superseded,
243
# this is the release that they are superseded by.
244
current_dominant = pub
245
dominant_version = version
246
elif current_dominant is None:
247
# This publication is no longer live, but there is no
248
# newer version to supersede it either. Therefore it
250
pub.requestDeletion(None)
252
# This publication is superseded. This is what we're
254
pub.supersede(current_dominant, logger=self.logger)
256
def _dominatePublications(self, pubs, generalization):
120
257
"""Perform dominations for the given publications.
259
Keep the latest published version for each package active,
260
superseding older versions.
122
262
:param pubs: A dict mapping names to a list of publications. Every
123
263
publication must be PUBLISHED or PENDING, and the first in each
124
264
list will be treated as dominant (so should be the latest).
265
:param generalization: A `GeneralizedPublication` helper representing
266
the kind of publications these are--source or binary.
126
268
self.logger.debug("Dominating packages...")
128
for name in pubs.keys():
130
"Empty list of publications for %s" % name)
131
for pubrec in pubs[name][1:]:
132
pubrec.supersede(pubs[name][0], logger=self.logger)
134
def _sortPackages(self, pkglist, is_source=True):
269
for name, publications in pubs.iteritems():
270
assert publications, "Empty list of publications for %s." % name
271
# Since this always picks the latest version as the live
272
# one, this dominatePackage call will never result in a
274
latest_version = generalization.getPackageVersion(publications[0])
275
self.dominatePackage(
276
publications, [latest_version], generalization)
278
def _sortPackages(self, pkglist, generalization):
135
279
"""Map out packages by name, and sort by descending version.
137
281
:param pkglist: An iterable of `SourcePackagePublishingHistory` or
138
282
`BinaryPackagePublishingHistory`.
139
:param is_source: Whether this call involves source package
140
publications. If so, work with `SourcePackagePublishingHistory`.
141
If not, work with `BinaryPackagepublishingHistory`.
142
:return: A dict mapping each package name (as UTF-8 encoded string)
143
to a list of publications from `pkglist`, newest first.
283
:param generalization: A `GeneralizedPublication` helper representing
284
the kind of publications these are--source or binary.
285
:return: A dict mapping each package name to a list of publications
286
from `pkglist`, newest first.
145
288
self.logger.debug("Sorting packages...")
148
get_release = operator.attrgetter("sourcepackagerelease")
149
get_name = operator.attrgetter("sourcepackagename")
151
get_release = operator.attrgetter("binarypackagerelease")
152
get_name = operator.attrgetter("binarypackagename")
155
291
for inpkg in pkglist:
156
key = get_name(get_release(inpkg)).name.encode('utf-8')
292
key = generalization.getPackageName(inpkg)
157
293
outpkgs.setdefault(key, []).append(inpkg)
159
sort_order = functools.partial(
160
_compare_packages_by_version_and_date, get_release)
161
295
for package_pubs in outpkgs.itervalues():
162
package_pubs.sort(cmp=sort_order, reverse=True)
296
package_pubs.sort(cmp=generalization.compare, reverse=True)
322
458
BinaryPackageFormat.DDEB,
323
459
bpph_location_clauses)
324
460
self.logger.debug("Dominating binaries...")
325
self._dominatePublications(self._sortPackages(binaries, False))
461
self._dominatePublications(
462
self._sortPackages(binaries, generalization), generalization)
464
def _composeActiveSourcePubsCondition(self, distroseries, pocket):
465
"""Compose ORM condition for restricting relevant source pubs."""
466
# Avoid circular imports.
467
from lp.soyuz.model.publishing import SourcePackagePublishingHistory
470
SourcePackagePublishingHistory.status ==
471
PackagePublishingStatus.PUBLISHED,
472
SourcePackagePublishingHistory.distroseries == distroseries,
473
SourcePackagePublishingHistory.archive == self.archive,
474
SourcePackagePublishingHistory.pocket == pocket,
327
477
def dominateSources(self, distroseries, pocket):
328
478
"""Perform domination on source package publications.
333
483
# Avoid circular imports.
334
484
from lp.soyuz.model.publishing import SourcePackagePublishingHistory
486
generalization = GeneralizedPublication(is_source=True)
335
488
self.logger.debug(
336
489
"Performing domination across %s/%s (Source)",
337
490
distroseries.name, pocket.title)
338
spph_location_clauses = And(
339
SourcePackagePublishingHistory.status ==
340
PackagePublishingStatus.PUBLISHED,
341
SourcePackagePublishingHistory.distroseries == distroseries,
342
SourcePackagePublishingHistory.archive == self.archive,
343
SourcePackagePublishingHistory.pocket == pocket,
492
spph_location_clauses = self._composeActiveSourcePubsCondition(
493
distroseries, pocket)
494
having_multiple_active_publications = (
495
Count(SourcePackagePublishingHistory.id) > 1)
345
496
candidate_source_names = Select(
346
497
SourcePackageName.id,
348
SourcePackageRelease.sourcepackagenameID ==
349
SourcePackageName.id,
350
SourcePackagePublishingHistory.sourcepackagereleaseID ==
351
SourcePackageRelease.id,
352
spph_location_clauses,
498
And(join_spph_spr(), join_spr_spn(), spph_location_clauses),
354
499
group_by=SourcePackageName.id,
355
having=Count(SourcePackagePublishingHistory.id) > 1)
356
sources = IMasterStore(SourcePackagePublishingHistory).find(
500
having=having_multiple_active_publications)
501
sources = IStore(SourcePackagePublishingHistory).find(
357
502
SourcePackagePublishingHistory,
358
SourcePackageRelease.id ==
359
SourcePackagePublishingHistory.sourcepackagereleaseID,
360
504
SourcePackageRelease.sourcepackagenameID.is_in(
361
505
candidate_source_names),
362
506
spph_location_clauses)
363
508
self.logger.debug("Dominating sources...")
364
self._dominatePublications(self._sortPackages(sources))
509
self._dominatePublications(
510
self._sortPackages(sources, generalization), generalization)
365
511
flush_database_updates()
513
def findPublishedSourcePackageNames(self, distroseries, pocket):
514
"""Find names of currently published source packages."""
515
result = IStore(SourcePackageName).find(
516
SourcePackageName.name,
519
self._composeActiveSourcePubsCondition(distroseries, pocket))
520
return result.config(distinct=True)
522
def findPublishedSPPHs(self, distroseries, pocket, package_name):
523
"""Find currently published source publications for given package."""
524
# Avoid circular imports.
525
from lp.soyuz.model.publishing import SourcePackagePublishingHistory
527
return IStore(SourcePackagePublishingHistory).find(
528
SourcePackagePublishingHistory,
531
SourcePackageName.name == package_name,
532
self._composeActiveSourcePubsCondition(distroseries, pocket))
534
def dominateRemovedSourceVersions(self, distroseries, pocket,
535
package_name, live_versions):
536
"""Dominate source publications based on a set of "live" versions.
538
Active publications for the "live" versions will remain active. All
539
other active publications for the same package (and the same archive,
540
distroseries, and pocket) are marked superseded.
542
Unlike traditional domination, this allows multiple versions of a
543
package to stay active in the same distroseries, archive, and pocket.
545
:param distroseries: `DistroSeries` to dominate.
546
:param pocket: `PackagePublishingPocket` to dominate.
547
:param package_name: Source package name, as text.
548
:param live_versions: Iterable of all version strings that are to
551
generalization = GeneralizedPublication(is_source=True)
552
pubs = self.findPublishedSPPHs(distroseries, pocket, package_name)
553
self.dominatePackage(pubs, live_versions, generalization)
367
555
def judge(self, distroseries, pocket):
368
556
"""Judge superseded sources and binaries."""
369
557
# Avoid circular imports.