1
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
1
# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
4
# pylint: disable-msg=E0611,W0212
32
from storm.zope import IResultSet
33
32
from zope.component import getUtility
34
33
from zope.interface import implements
34
from zope.security.proxy import removeSecurityProxy
36
from canonical.config import config
37
from canonical.database.sqlbase import (
42
from canonical.launchpad.browser.librarian import ProxiedLibraryFileAlias
43
from canonical.launchpad.components.decoratedresultset import (
46
from canonical.launchpad.database.librarian import (
50
from canonical.launchpad.helpers import (
51
get_contact_email_addresses,
54
from canonical.launchpad.interfaces.lpstorm import (
59
from canonical.launchpad.mail import (
63
from canonical.launchpad.webapp import canonical_url
64
from canonical.launchpad.webapp.interfaces import (
36
69
from lp.app.browser.tales import DurationFormatterAPI
37
70
from lp.app.errors import NotFoundError
38
71
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
50
83
PackageBuildDerived,
52
from lp.services.config import config
53
from lp.services.database.bulk import load_related
54
from lp.services.database.decoratedresultset import DecoratedResultSet
55
from lp.services.database.lpstorm import (
60
from lp.services.database.sqlbase import (
65
85
from lp.services.job.model.job import Job
66
from lp.services.librarian.browser import ProxiedLibraryFileAlias
67
from lp.services.librarian.model import (
71
from lp.services.mail.helpers import (
72
get_contact_email_addresses,
75
from lp.services.mail.sendmail import (
79
from lp.services.webapp import canonical_url
80
from lp.services.webapp.interfaces import (
85
86
from lp.soyuz.enums import ArchivePurpose
86
87
from lp.soyuz.interfaces.binarypackagebuild import (
145
146
def current_component(self):
146
147
"""See `IBuild`."""
147
148
latest_publication = self._getLatestPublication()
148
# Production has some buggy builds without source publications.
149
# They seem to have been created by early versions of gina and
150
# the readding of hppa.
151
if latest_publication is not None:
152
return latest_publication.component
149
assert latest_publication is not None, (
150
'Build %d lacks a corresponding source publication.' % self.id)
151
return latest_publication.component
155
154
def current_source_publication(self):
350
349
"""See `IBuild`."""
351
350
return self.status is BuildStatus.NEEDSBUILD
354
def can_be_cancelled(self):
356
if not self.buildqueue_record:
358
if self.buildqueue_record.virtualized is False:
361
cancellable_statuses = [
362
BuildStatus.BUILDING,
363
BuildStatus.NEEDSBUILD,
365
return self.status in cancellable_statuses
368
353
"""See `IBuild`."""
369
354
assert self.can_be_retried, "Build %s cannot be retried" % self.id
393
378
return self.buildqueue_record.lastscore
396
"""See `IBinaryPackageBuild`."""
397
if not self.can_be_cancelled:
400
# If the build is currently building we need to tell the
401
# buildd-manager to terminate it.
402
if self.status == BuildStatus.BUILDING:
403
self.status = BuildStatus.CANCELLING
406
# Otherwise we can cancel it here.
407
self.buildqueue_record.cancel()
409
380
def makeJob(self):
410
381
"""See `IBuildFarmJob`."""
411
382
store = Store.of(self)
433
404
"It is expected to be a tuple containing only another "
434
405
"tuple with 3 elements (name, version, relation)."
435
406
% (token, self.title, self.id, self.dependencies))
436
# Map relations to the canonical form used in control files.
439
elif relation == '>':
441
407
return (name, version, relation)
443
409
def _checkDependencyVersion(self, available, required, relation):
444
410
"""Return True if the available version satisfies the context."""
445
411
# This dict maps the package version relationship syntax in lambda
446
412
# functions which returns boolean according the results of
447
# apt_pkg.version_compare function (see the order above).
413
# apt_pkg.VersionCompare function (see the order above).
448
414
# For further information about pkg relationship syntax see:
450
416
# http://www.debian.org/doc/debian-policy/ch-relationships.html
468
434
# it behaves similar to cmp, i.e. returns negative
469
435
# if first < second, zero if first == second and
470
436
# positive if first > second.
471
dep_result = apt_pkg.version_compare(available, required)
437
dep_result = apt_pkg.VersionCompare(available, required)
473
439
return version_relation_map[relation](dep_result)
505
471
def updateDependencies(self):
506
472
"""See `IBuild`."""
508
# apt_pkg requires init_system to get version_compare working
510
apt_pkg.init_system()
474
# apt_pkg requires InitSystem to get VersionCompare working properly.
512
477
# Check package build dependencies using apt_pkg
514
parsed_deps = apt_pkg.parse_depends(self.dependencies)
479
parsed_deps = apt_pkg.ParseDepends(self.dependencies)
515
480
except (ValueError, TypeError):
516
481
raise UnparsableDependencies(
517
482
"Build dependencies for %s (%s) could not be parsed: '%s'\n"
605
570
# Analysis of previous build data shows that a build rate
606
571
# of 6 KB/second is realistic. Furthermore we have to add
607
572
# another minute for generic build overhead.
608
estimate = int(package_size / 6.0 / 60 + 1)
573
estimate = int(package_size/6.0/60 + 1)
610
575
# No historic build times and no package size available,
611
576
# assume a build time of 5 minutes.
755
template = get_email_template('build-notification.txt', app='soyuz')
720
template = get_email_template('build-notification.txt')
757
722
'source_name': self.source_package_release.name,
758
723
'source_version': self.source_package_release.version,
872
837
return resulting_tuple[0]
874
def preloadBuildsData(self, builds):
876
from lp.soyuz.model.distroarchseries import (
879
from lp.registry.model.distroseries import (
882
from lp.registry.model.distribution import (
885
from lp.soyuz.model.archive import Archive
886
from lp.registry.model.person import Person
887
self._prefetchBuildData(builds)
888
distro_arch_series = load_related(
889
DistroArchSeries, builds, ['distro_arch_series_id'])
890
package_builds = load_related(
891
PackageBuild, builds, ['package_build_id'])
892
archives = load_related(Archive, package_builds, ['archive_id'])
893
load_related(Person, archives, ['ownerID'])
894
distroseries = load_related(
895
DistroSeries, distro_arch_series, ['distroseriesID'])
897
Distribution, distroseries, ['distributionID'])
899
def getByBuildFarmJobs(self, build_farm_jobs):
900
"""See `ISpecificBuildFarmJobSource`."""
901
if len(build_farm_jobs) == 0:
902
return EmptyResultSet()
903
clause_tables = (BinaryPackageBuild, PackageBuild, BuildFarmJob)
904
build_farm_job_ids = [
905
build_farm_job.id for build_farm_job in build_farm_jobs]
907
resultset = Store.of(build_farm_jobs[0]).using(*clause_tables).find(
909
BinaryPackageBuild.package_build == PackageBuild.id,
910
PackageBuild.build_farm_job == BuildFarmJob.id,
911
BuildFarmJob.id.is_in(build_farm_job_ids))
912
return DecoratedResultSet(
913
resultset, pre_iter_hook=self.preloadBuildsData)
915
839
def getPendingBuildsForArchSet(self, archseries):
916
840
"""See `IBinaryPackageBuildSet`."""
917
841
if not archseries:
938
862
:param tables: container to which to add joined tables.
939
863
:param status: optional build state for which to add a query clause if
941
:param name: optional source package release name (or list of source
942
package release names) for which to add a query clause if
944
:param pocket: optional pocket (or list of pockets) for which to add a
865
:param name: optional source package release name for which to add a
945
866
query clause if present.
867
:param pocket: optional pocket for which to add a query clause if
946
869
:param arch_tag: optional architecture tag for which to add a
947
870
query clause if present.
961
884
# Add query clause that filters on pocket if the latter is provided.
963
if not isinstance(pocket, (list, tuple)):
966
queries.append('PackageBuild.pocket IN %s' % sqlvalues(pocket))
886
queries.append('PackageBuild.pocket=%s' % sqlvalues(pocket))
968
888
# Add query clause that filters on architecture tag if provided.
969
889
if arch_tag is not None:
976
896
# Add query clause that filters on source package release name if the
977
897
# latter is provided.
978
898
if name is not None:
979
if not isinstance(name, (list, tuple)):
981
BinaryPackageBuild.source_package_release =
982
SourcePackageRelease.id AND
983
SourcePackageRelease.sourcepackagename =
985
AND SourcepackageName.name LIKE '%%' || %s || '%%'
986
''' % quote_like(name))
989
BinaryPackageBuild.source_package_release =
990
SourcePackageRelease.id AND
991
SourcePackageRelease.sourcepackagename =
993
AND SourcepackageName.name IN %s
994
''' % sqlvalues(name))
900
BinaryPackageBuild.source_package_release =
901
SourcePackageRelease.id AND
902
SourcePackageRelease.sourcepackagename = SourcePackageName.id
903
AND SourcepackageName.name LIKE '%%' || %s || '%%'
904
''' % quote_like(name))
995
905
tables.extend(['SourcePackageRelease', 'SourcePackageName'])
997
907
def getBuildsForBuilder(self, builder_id, status=None, name=None,
1091
1001
# Ordering according status
1092
1002
# * NEEDSBUILD, BUILDING & UPLOADING by -lastscore
1093
# * SUPERSEDED & All by -PackageBuild.build_farm_job
1094
# (nearly equivalent to -datecreated, but much more
1003
# * SUPERSEDED & All by -datecreated
1096
1004
# * FULLYBUILT & FAILURES by -datebuilt
1097
1005
# It should present the builds in a more natural order.
1099
1007
BuildStatus.NEEDSBUILD,
1100
1008
BuildStatus.BUILDING,
1101
1009
BuildStatus.UPLOADING]:
1102
order_by = [Desc(BuildQueue.lastscore), BinaryPackageBuild.id]
1103
order_by_table = BuildQueue
1010
orderBy = ["-BuildQueue.lastscore", "BinaryPackageBuild.id"]
1104
1011
clauseTables.append('BuildQueue')
1105
1012
clauseTables.append('BuildPackageJob')
1106
1013
condition_clauses.append(
1107
1014
'BuildPackageJob.build = BinaryPackageBuild.id')
1108
1015
condition_clauses.append('BuildPackageJob.job = BuildQueue.job')
1109
1016
elif status == BuildStatus.SUPERSEDED or status is None:
1110
order_by = [Desc(PackageBuild.build_farm_job_id)]
1111
order_by_table = PackageBuild
1017
orderBy = ["-BuildFarmJob.date_created"]
1113
order_by = [Desc(BuildFarmJob.date_finished),
1114
BinaryPackageBuild.id]
1115
order_by_table = BuildFarmJob
1019
orderBy = ["-BuildFarmJob.date_finished"]
1117
1021
# End of duplication (see XXX cprov 2006-09-25 above).
1125
1029
"PackageBuild.archive IN %s" %
1126
1030
sqlvalues(list(distribution.all_distro_archive_ids)))
1128
store = Store.of(distribution)
1129
clauseTables = [BinaryPackageBuild] + clauseTables
1130
result_set = store.using(*clauseTables).find(
1131
(BinaryPackageBuild, order_by_table), *condition_clauses)
1132
result_set.order_by(*order_by)
1134
def get_bpp(result_row):
1135
return result_row[0]
1137
1032
return self._decorate_with_prejoins(
1138
DecoratedResultSet(result_set, result_decorator=get_bpp))
1033
BinaryPackageBuild.select(' AND '.join(condition_clauses),
1034
clauseTables=clauseTables, orderBy=orderBy))
1140
1036
def _decorate_with_prejoins(self, result_set):
1141
1037
"""Decorate build records with related data prefetch functionality."""
1142
1038
# Grab the native storm result set.
1143
result_set = IResultSet(result_set)
1039
result_set = removeSecurityProxy(result_set)._result_set
1144
1040
decorated_results = DecoratedResultSet(
1145
1041
result_set, pre_iter_hook=self._prefetchBuildData)
1146
1042
return decorated_results