~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/soyuz/model/binarypackagebuild.py

  • Committer: Steve Kowalik
  • Date: 2011-08-07 04:05:52 UTC
  • mto: This revision was merged to the branch mainline in revision 13626.
  • Revision ID: stevenk@ubuntu.com-20110807040552-mwnxo0flmhvl35e8
Correct the notification based on review comments, and remove request{,ed}
from the function names, switching to create{,d}.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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).
3
3
 
4
4
# pylint: disable-msg=E0611,W0212
29
29
    EmptyResultSet,
30
30
    Store,
31
31
    )
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
35
35
 
 
36
from canonical.config import config
 
37
from canonical.database.sqlbase import (
 
38
    quote_like,
 
39
    SQLBase,
 
40
    sqlvalues,
 
41
    )
 
42
from canonical.launchpad.browser.librarian import ProxiedLibraryFileAlias
 
43
from canonical.launchpad.components.decoratedresultset import (
 
44
    DecoratedResultSet,
 
45
    )
 
46
from canonical.launchpad.database.librarian import (
 
47
    LibraryFileAlias,
 
48
    LibraryFileContent,
 
49
    )
 
50
from canonical.launchpad.helpers import (
 
51
    get_contact_email_addresses,
 
52
    get_email_template,
 
53
    )
 
54
from canonical.launchpad.interfaces.lpstorm import (
 
55
    IMasterObject,
 
56
    ISlaveStore,
 
57
    IStore,
 
58
    )
 
59
from canonical.launchpad.mail import (
 
60
    format_address,
 
61
    simple_sendmail,
 
62
    )
 
63
from canonical.launchpad.webapp import canonical_url
 
64
from canonical.launchpad.webapp.interfaces import (
 
65
    DEFAULT_FLAVOR,
 
66
    IStoreSelector,
 
67
    MAIN_STORE,
 
68
    )
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
49
82
    PackageBuild,
50
83
    PackageBuildDerived,
51
84
    )
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 (
56
 
    IMasterObject,
57
 
    ISlaveStore,
58
 
    IStore,
59
 
    )
60
 
from lp.services.database.sqlbase import (
61
 
    quote_like,
62
 
    SQLBase,
63
 
    sqlvalues,
64
 
    )
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 (
68
 
    LibraryFileAlias,
69
 
    LibraryFileContent,
70
 
    )
71
 
from lp.services.mail.helpers import (
72
 
    get_contact_email_addresses,
73
 
    get_email_template,
74
 
    )
75
 
from lp.services.mail.sendmail import (
76
 
    format_address,
77
 
    simple_sendmail,
78
 
    )
79
 
from lp.services.webapp import canonical_url
80
 
from lp.services.webapp.interfaces import (
81
 
    DEFAULT_FLAVOR,
82
 
    IStoreSelector,
83
 
    MAIN_STORE,
84
 
    )
85
86
from lp.soyuz.enums import ArchivePurpose
86
87
from lp.soyuz.interfaces.binarypackagebuild import (
87
88
    BuildSetStatus,
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
153
152
 
154
153
    @property
155
154
    def current_source_publication(self):
350
349
        """See `IBuild`."""
351
350
        return self.status is BuildStatus.NEEDSBUILD
352
351
 
353
 
    @property
354
 
    def can_be_cancelled(self):
355
 
        """See `IBuild`."""
356
 
        if not self.buildqueue_record:
357
 
            return False
358
 
        if self.buildqueue_record.virtualized is False:
359
 
            return False
360
 
 
361
 
        cancellable_statuses = [
362
 
            BuildStatus.BUILDING,
363
 
            BuildStatus.NEEDSBUILD,
364
 
            ]
365
 
        return self.status in cancellable_statuses
366
 
 
367
352
    def retry(self):
368
353
        """See `IBuild`."""
369
354
        assert self.can_be_retried, "Build %s cannot be retried" % self.id
392
377
        else:
393
378
            return self.buildqueue_record.lastscore
394
379
 
395
 
    def cancel(self):
396
 
        """See `IBinaryPackageBuild`."""
397
 
        if not self.can_be_cancelled:
398
 
            return
399
 
 
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
404
 
            return
405
 
 
406
 
        # Otherwise we can cancel it here.
407
 
        self.buildqueue_record.cancel()
408
 
 
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.
437
 
        if relation == '<':
438
 
            relation = '<<'
439
 
        elif relation == '>':
440
 
            relation = '>>'
441
407
        return (name, version, relation)
442
408
 
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:
449
415
        #
450
416
        # http://www.debian.org/doc/debian-policy/ch-relationships.html
452
418
        version_relation_map = {
453
419
            # any version is acceptable if no relationship is given
454
420
            '': lambda x: True,
455
 
            # strictly later
 
421
            # stricly later
456
422
            '>>': lambda x: x == 1,
457
423
            # later or equal
458
424
            '>=': lambda x: x >= 0,
459
 
            # strictly equal
 
425
            # stricly equal
460
426
            '=': lambda x: x == 0,
461
427
            # earlier or equal
462
428
            '<=': lambda x: x <= 0,
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)
472
438
 
473
439
        return version_relation_map[relation](dep_result)
474
440
 
505
471
    def updateDependencies(self):
506
472
        """See `IBuild`."""
507
473
 
508
 
        # apt_pkg requires init_system to get version_compare working
509
 
        # properly.
510
 
        apt_pkg.init_system()
 
474
        # apt_pkg requires InitSystem to get VersionCompare working properly.
 
475
        apt_pkg.InitSystem()
511
476
 
512
477
        # Check package build dependencies using apt_pkg
513
478
        try:
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)
609
574
            else:
610
575
                # No historic build times and no package size available,
611
576
                # assume a build time of 5 minutes.
752
717
        else:
753
718
            extra_info = ''
754
719
 
755
 
        template = get_email_template('build-notification.txt', app='soyuz')
 
720
        template = get_email_template('build-notification.txt')
756
721
        replacements = {
757
722
            'source_name': self.source_package_release.name,
758
723
            'source_version': self.source_package_release.version,
871
836
            return None
872
837
        return resulting_tuple[0]
873
838
 
874
 
    def preloadBuildsData(self, builds):
875
 
        # Circular imports.
876
 
        from lp.soyuz.model.distroarchseries import (
877
 
            DistroArchSeries
878
 
            )
879
 
        from lp.registry.model.distroseries import (
880
 
            DistroSeries
881
 
            )
882
 
        from lp.registry.model.distribution import (
883
 
            Distribution
884
 
            )
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'])
896
 
        load_related(
897
 
            Distribution, distroseries, ['distributionID'])
898
 
 
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]
906
 
 
907
 
        resultset = Store.of(build_farm_jobs[0]).using(*clause_tables).find(
908
 
            BinaryPackageBuild,
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)
914
 
 
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
940
864
            present.
941
 
        :param name: optional source package release name (or list of source
942
 
            package release names) for which to add a query clause if
943
 
            present.
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
 
868
            present.
946
869
        :param arch_tag: optional architecture tag for which to add a
947
870
            query clause if present.
948
871
        """
960
883
 
961
884
        # Add query clause that filters on pocket if the latter is provided.
962
885
        if pocket:
963
 
            if not isinstance(pocket, (list, tuple)):
964
 
                pocket = (pocket,)
965
 
 
966
 
            queries.append('PackageBuild.pocket IN %s' % sqlvalues(pocket))
 
886
            queries.append('PackageBuild.pocket=%s' % sqlvalues(pocket))
967
887
 
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)):
980
 
                queries.append('''
981
 
                    BinaryPackageBuild.source_package_release =
982
 
                        SourcePackageRelease.id AND
983
 
                    SourcePackageRelease.sourcepackagename =
984
 
                        SourcePackageName.id
985
 
                    AND SourcepackageName.name LIKE '%%' || %s || '%%'
986
 
                ''' % quote_like(name))
987
 
            else:
988
 
                queries.append('''
989
 
                    BinaryPackageBuild.source_package_release =
990
 
                        SourcePackageRelease.id AND
991
 
                    SourcePackageRelease.sourcepackagename =
992
 
                        SourcePackageName.id
993
 
                    AND SourcepackageName.name IN %s
994
 
                ''' % sqlvalues(name))
 
899
            queries.append('''
 
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'])
996
906
 
997
907
    def getBuildsForBuilder(self, builder_id, status=None, name=None,
1090
1000
 
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
1095
 
        #   efficient.)
 
1003
        # * SUPERSEDED & All by -datecreated
1096
1004
        # * FULLYBUILT & FAILURES by -datebuilt
1097
1005
        # It should present the builds in a more natural order.
1098
1006
        if status in [
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"]
1112
1018
        else:
1113
 
            order_by = [Desc(BuildFarmJob.date_finished),
1114
 
                        BinaryPackageBuild.id]
1115
 
            order_by_table = BuildFarmJob
 
1019
            orderBy = ["-BuildFarmJob.date_finished"]
1116
1020
 
1117
1021
        # End of duplication (see XXX cprov 2006-09-25 above).
1118
1022
 
1125
1029
            "PackageBuild.archive IN %s" %
1126
1030
            sqlvalues(list(distribution.all_distro_archive_ids)))
1127
1031
 
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)
1133
 
 
1134
 
        def get_bpp(result_row):
1135
 
            return result_row[0]
1136
 
 
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))
1139
1035
 
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