Merge lp:~michael.nelson/launchpad/536700-present-packagebuilds-in-ppa-context into lp:launchpad/db-devel

Proposed by Michael Nelson
Status: Merged
Merged at revision: 9485
Proposed branch: lp:~michael.nelson/launchpad/536700-present-packagebuilds-in-ppa-context
Merge into: lp:launchpad/db-devel
Diff against target: 1137 lines (+424/-145) (has conflicts)
28 files modified
lib/canonical/buildd/apply-ogre-model (+0/-39)
lib/canonical/buildd/binarypackage.py (+7/-17)
lib/canonical/buildd/debian.py (+1/-40)
lib/canonical/buildd/debian/changelog (+7/-0)
lib/canonical/buildd/debian/rules (+2/-2)
lib/canonical/buildd/debian/upgrade-config (+11/-0)
lib/canonical/buildd/template-buildd-slave.conf (+0/-1)
lib/canonical/buildd/tests/buildd-slave-test.conf (+0/-1)
lib/canonical/launchpad/templates/launchpad-form.pt (+6/-3)
lib/canonical/widgets/product.py (+3/-1)
lib/lp/buildmaster/configure.zcml (+6/-0)
lib/lp/buildmaster/interfaces/packagebuild.py (+26/-1)
lib/lp/buildmaster/model/packagebuild.py (+45/-2)
lib/lp/buildmaster/tests/test_packagebuild.py (+49/-6)
lib/lp/code/browser/sourcepackagerecipe.py (+12/-3)
lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+56/-7)
lib/lp/code/model/sourcepackagerecipedata.py (+2/-0)
lib/lp/registry/doc/product-widgets.txt (+45/-2)
lib/lp/registry/model/sourcepackage.py (+5/-3)
lib/lp/soyuz/configure.zcml (+5/-0)
lib/lp/soyuz/interfaces/archive.py (+6/-0)
lib/lp/soyuz/interfaces/buildrecords.py (+11/-6)
lib/lp/soyuz/model/archive.py (+14/-4)
lib/lp/soyuz/model/binarypackagebuild.py (+21/-0)
lib/lp/soyuz/tests/test_archive.py (+1/-1)
lib/lp/soyuz/tests/test_binarypackagebuild.py (+37/-0)
lib/lp/soyuz/tests/test_hasbuildrecords.py (+31/-0)
lib/lp/testing/factory.py (+15/-6)
Text conflict in lib/lp/code/browser/tests/test_sourcepackagerecipe.py
To merge this branch: bzr merge lp:~michael.nelson/launchpad/536700-present-packagebuilds-in-ppa-context
Reviewer Review Type Date Requested Status
Henning Eggers (community) code Approve
Review via email: mp+27477@code.launchpad.net

Description of the change

Please review my branch:

   bzr+ssh://bazaar.launchpad.net/~michael.nelson/launchpad/536700-present-packagebuilds-in-ppa-context

revision 11006.

Test command: bin/test -vv -m test_packagebuild -m test_binarypackagebuild -m
test_hasbuildrecords

This branch does some initial work to enable PPA's to present general
IPackageBuilds rather than IBinaryPackageBuilds (bug 536700).

It adds and tests an IPackageBuildSet.getBuildsForArchive(). It adds an
optional param to the getBuildRecords() method - binary_only - which defaults
to true (current behaivour). It then updates IArchive.getBuildRecords() to
support binary_only=False.

Additionally, it adds an adapter for IBuildFarmJob->IBinaryPackageBuild in
preparation for the UI (so each build can present its title correctly).

To post a comment you must log in.
Revision history for this message
Henning Eggers (henninge) wrote :
Download full text (19.4 KiB)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi Michael,
thank you for your fix and sorry for the delay...

Am 14.06.2010 10:35, schrieb Michael Nelson:
> Test command: bin/test -vv -m test_packagebuild -m test_binarypackagebuild -m
> test_hasbuildrecords

Yup, they pass. Thanks.

> This branch does some initial work to enable PPA's to present general
> IPackageBuilds rather than IBinaryPackageBuilds (bug 536700).

So, I assume that is the reason that no bug is attached to this branch?

>
> It adds and tests an IPackageBuildSet.getBuildsForArchive(). It adds an
> optional param to the getBuildRecords() method - binary_only - which defaults
> to true (current behaivour). It then updates IArchive.getBuildRecords() to
> support binary_only=False.
>
> Additionally, it adds an adapter for IBuildFarmJob->IBinaryPackageBuild in
> preparation for the UI (so each build can present its title correctly).
>

Very clever. ;-)

Here are my comments:

> === modified file 'lib/lp/buildmaster/configure.zcml'

OK.

> === modified file 'lib/lp/buildmaster/interfaces/packagebuild.py'

OK.

> === modified file 'lib/lp/buildmaster/model/packagebuild.py'
> --- lib/lp/buildmaster/model/packagebuild.py 2010-06-07 10:43:01 +0000
> +++ lib/lp/buildmaster/model/packagebuild.py 2010-06-14 08:35:42 +0000
> @@ -11,6 +11,7 @@
> from lazr.delegates import delegates
>
> from storm.locals import Int, Reference, Storm, Unicode
> +from storm.expr import Desc
>
> from zope.component import getUtility
> from zope.interface import classProvides, implements
> @@ -19,13 +20,16 @@
> from canonical.launchpad.browser.librarian import (
> ProxiedLibraryFileAlias)
> from canonical.launchpad.interfaces.lpstorm import IMasterStore
> +from canonical.launchpad.webapp.interfaces import (
> + IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
>
> from lp.buildmaster.interfaces.buildbase import BuildStatus
> from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource
> from lp.buildmaster.interfaces.packagebuild import (
> - IPackageBuild, IPackageBuildSource)
> + IPackageBuild, IPackageBuildSet, IPackageBuildSource)
> from lp.buildmaster.model.buildbase import handle_status_for_build, BuildBase
> -from lp.buildmaster.model.buildfarmjob import BuildFarmJobDerived
> +from lp.buildmaster.model.buildfarmjob import (
> + BuildFarmJob, BuildFarmJobDerived)
> from lp.registry.interfaces.pocket import PackagePublishingPocket
> from lp.soyuz.adapters.archivedependencies import (
> default_component_dependency_name)
> @@ -214,3 +218,40 @@
> def _handleStatus_GIVENBACK(self, librarian, slave_status, logger):
> return BuildBase._handleStatus_GIVENBACK(
> self, librarian, slave_status, logger)
> +
> +
> +class PackageBuildSet:
> + implements(IPackageBuildSet)
> +
> + def getBuildsForArchive(self, archive, status=None, pocket=None):
> + """See `IPackageBuildSet`."""
> +
> + extra_exprs = []
> +
> + # Add query clause that filters on build status if the latter is
> + # provided.

I had been told that comments are not there to reiterate what the code is
doing - the code should speak for itself. Thi...

review: Needs Fixing (code)
Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (19.3 KiB)

> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> Hi Michael,
> thank you for your fix and sorry for the delay...

No problem... it was worth all the improvements below :)

>
> Am 14.06.2010 10:35, schrieb Michael Nelson:
> > Test command: bin/test -vv -m test_packagebuild -m test_binarypackagebuild
> -m
> > test_hasbuildrecords
>
> Yup, they pass. Thanks.
>
> > This branch does some initial work to enable PPA's to present general
> > IPackageBuilds rather than IBinaryPackageBuilds (bug 536700).
>
> So, I assume that is the reason that no bug is attached to this branch?

There are two now :P

>
> >
> > It adds and tests an IPackageBuildSet.getBuildsForArchive(). It adds an
> > optional param to the getBuildRecords() method - binary_only - which
> defaults
> > to true (current behaivour). It then updates IArchive.getBuildRecords() to
> > support binary_only=False.
> >
> > Additionally, it adds an adapter for IBuildFarmJob->IBinaryPackageBuild in
> > preparation for the UI (so each build can present its title correctly).
> >
>
> Very clever. ;-)
>
> Here are my comments:
>
...
> > === modified file 'lib/lp/buildmaster/model/packagebuild.py'
> > --- lib/lp/buildmaster/model/packagebuild.py 2010-06-07 10:43:01 +0000
> > +++ lib/lp/buildmaster/model/packagebuild.py 2010-06-14 08:35:42 +0000
> > @@ -214,3 +218,40 @@
> > def _handleStatus_GIVENBACK(self, librarian, slave_status, logger):
> > return BuildBase._handleStatus_GIVENBACK(
> > self, librarian, slave_status, logger)
> > +
> > +
> > +class PackageBuildSet:
> > + implements(IPackageBuildSet)
> > +
> > + def getBuildsForArchive(self, archive, status=None, pocket=None):
> > + """See `IPackageBuildSet`."""
> > +
> > + extra_exprs = []
> > +
> > + # Add query clause that filters on build status if the latter is
> > + # provided.
>
> I had been told that comments are not there to reiterate what the code is
> doing - the code should speak for itself. This looks like such a case, does it
> not?

/me must stop doing that. Removed.

>
> > + if status is not None:
> > + extra_exprs.append(BuildFarmJob.status == status)
> > +
> > + # Add query clause that filters on pocket if the latter is
> provided.
>
> Same here. The comment is just chatter. Maybe its pseudo code that you forgot
> in here?

Ditto.

>
> > + if pocket:
> > + extra_exprs.append(PackageBuild.pocket == pocket)
> > +
> > + store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
> > + result_set = store.find(PackageBuild,
> > + PackageBuild.archive == archive,
> > + PackageBuild.build_farm_job == BuildFarmJob.id,
> > + *extra_exprs)
> > +
> > + # Ordering according status
> > + # * SUPERSEDED & All by -datecreated
>
> What does "& All" refer to"?

I updated this to: When we have a set of builds that may include pending or superseded builds, we order by -date_created (as we won't always have a date_finished), otherwise we can order by -date_finished.

>
> > + # * FULLYBUILT & FAILURES by -datebuilt
> I would put this near the "else" to expla...

Revision history for this message
Henning Eggers (henninge) wrote :

As discussed on mumble:

- Please make sure the indention is less confusing by using an intermediate variable for the states, e.g. "unfinished_states".

+ if status is None or status in [
+ BuildStatus.NEEDSBUILD,
+ BuildStatus.BUILDING,
+ BuildStatus.SUPERSEDED]:
             result_set.order_by(
                 Desc(BuildFarmJob.date_created), BuildFarmJob.id)

- AFAICT PackageBuildSet is missing from __all__ (not TestPackageBuildSet, sorry). Pleaes check that.

Thanks for you good work and patience with me. ;-)

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== removed file 'lib/canonical/buildd/apply-ogre-model'
--- lib/canonical/buildd/apply-ogre-model 2009-06-30 21:06:27 +0000
+++ lib/canonical/buildd/apply-ogre-model 1970-01-01 00:00:00 +0000
@@ -1,39 +0,0 @@
1#!/bin/sh
2#
3# Copyright 2009 Canonical Ltd. This software is licensed under the
4# GNU Affero General Public License version 3 (see the file LICENSE).
5#
6# Author: Daniel Silverstone <daniel.silverstone@canonical.com>
7
8# Buildd Slave tool to apply the ogre model to the chroot
9
10# Expects build id as arg 1, makes build-id to contain the build
11# Expects ogre component name as arg 2
12
13# Needs SUDO to be set to a sudo instance for passwordless access
14# LN must be an ln instance which can take -s and -f at the same time
15
16SUDO=/usr/bin/sudo
17
18BUILDID="$1"
19COMPONENT="$2"
20
21set -e
22
23exec 2>&1
24
25cd $HOME
26cd "build-$BUILDID/chroot-autobuild/etc/apt"
27
28echo "Attempting OGRE for $COMPONENT in build-$BUILDID"
29
30if [ -e "sources.list.$COMPONENT" ]; then
31 if [ -h "sources.list" ]; then
32 $SUDO ln -sf "sources.list.$COMPONENT" sources.list
33 else
34 echo "OGRE sources.list found, but sources.list is not a symlink"
35 fi
36else
37 echo "No OGRE sources.list found."
38fi
39
400
=== modified file 'lib/canonical/buildd/binarypackage.py'
--- lib/canonical/buildd/binarypackage.py 2010-01-14 02:55:22 +0000
+++ lib/canonical/buildd/binarypackage.py 2010-06-16 14:06:25 +0000
@@ -55,22 +55,12 @@
55 self._dscfile = f55 self._dscfile = f
56 if self._dscfile is None:56 if self._dscfile is None:
57 raise ValueError, files57 raise ValueError, files
58 if 'arch_indep' in extra_args:58
59 self.arch_indep = extra_args['arch_indep']59 self.archive_purpose = extra_args.get('archive_purpose')
60 else:60 self.suite = extra_args.get('suite')
61 self.arch_indep = False61 self.component = extra_args['ogrecomponent']
62 if 'suite' in extra_args:62 self.arch_indep = extra_args.get('arch_indep', False)
63 self.suite = extra_args['suite']63 self.build_debug_symbols = extra_args.get('build_debug_symbols', False)
64 else:
65 self.suite = False
66 if 'archive_purpose' in extra_args:
67 self.archive_purpose = extra_args['archive_purpose']
68 else:
69 self.archive_purpose = False
70 if 'build_debug_symbols' in extra_args:
71 self.build_debug_symbols = extra_args['build_debug_symbols']
72 else:
73 self.build_debug_symbols = False
7464
75 super(BinaryPackageBuildManager, self).initiate(65 super(BinaryPackageBuildManager, self).initiate(
76 files, chroot, extra_args)66 files, chroot, extra_args)
@@ -92,7 +82,7 @@
92 args.extend(["--purpose=" + self.archive_purpose])82 args.extend(["--purpose=" + self.archive_purpose])
93 if self.build_debug_symbols:83 if self.build_debug_symbols:
94 args.extend(["--build-debug-symbols"])84 args.extend(["--build-debug-symbols"])
95 args.extend(["--comp=" + self.ogre])85 args.extend(["--comp=" + self.component])
96 args.extend([self._dscfile])86 args.extend([self._dscfile])
97 self.runSubProcess( self._sbuildpath, args )87 self.runSubProcess( self._sbuildpath, args )
9888
9989
=== modified file 'lib/canonical/buildd/debian.py'
--- lib/canonical/buildd/debian.py 2010-01-14 20:19:27 +0000
+++ lib/canonical/buildd/debian.py 2010-06-16 14:06:25 +0000
@@ -20,7 +20,6 @@
20 INIT = "INIT"20 INIT = "INIT"
21 UNPACK = "UNPACK"21 UNPACK = "UNPACK"
22 MOUNT = "MOUNT"22 MOUNT = "MOUNT"
23 OGRE = "OGRE"
24 SOURCES = "SOURCES"23 SOURCES = "SOURCES"
25 UPDATE = "UPDATE"24 UPDATE = "UPDATE"
26 REAP = "REAP"25 REAP = "REAP"
@@ -35,7 +34,6 @@
35 BuildManager.__init__(self, slave, buildid)34 BuildManager.__init__(self, slave, buildid)
36 self._updatepath = slave._config.get("debianmanager", "updatepath")35 self._updatepath = slave._config.get("debianmanager", "updatepath")
37 self._scanpath = slave._config.get("debianmanager", "processscanpath")36 self._scanpath = slave._config.get("debianmanager", "processscanpath")
38 self._ogrepath = slave._config.get("debianmanager", "ogrepath")
39 self._sourcespath = slave._config.get("debianmanager", "sourcespath")37 self._sourcespath = slave._config.get("debianmanager", "sourcespath")
40 self._cachepath = slave._config.get("slave","filecache")38 self._cachepath = slave._config.get("slave","filecache")
41 self._state = DebianBuildState.INIT39 self._state = DebianBuildState.INIT
@@ -49,32 +47,15 @@
49 def initiate(self, files, chroot, extra_args):47 def initiate(self, files, chroot, extra_args):
50 """Initiate a build with a given set of files and chroot."""48 """Initiate a build with a given set of files and chroot."""
5149
52 if 'ogrecomponent' in extra_args:50 self.sources_list = extra_args.get('archives')
53 # Ubuntu refers to the concept that "main sees only main
54 # while building" etc as "The Ogre Model" (onions, layers
55 # and all). If we're given an ogre component, use it
56 self.ogre = extra_args['ogrecomponent']
57 else:
58 self.ogre = False
59 if 'archives' in extra_args and extra_args['archives']:
60 self.sources_list = extra_args['archives']
61 else:
62 self.sources_list = None
6351
64 BuildManager.initiate(self, files, chroot, extra_args)52 BuildManager.initiate(self, files, chroot, extra_args)
6553
66 def doOgreModel(self):
67 """Perform the ogre model activation."""
68 self.runSubProcess(self._ogrepath,
69 ["apply-ogre-model", self._buildid, self.ogre])
70
71 def doSourcesList(self):54 def doSourcesList(self):
72 """Override apt/sources.list.55 """Override apt/sources.list.
7356
74 Mainly used for PPA builds.57 Mainly used for PPA builds.
75 """58 """
76 # XXX cprov 2007-05-17: It 'undo' ogre-component changes.
77 # for PPAs it must be re-implemented on builddmaster side.
78 args = ["override-sources-list", self._buildid]59 args = ["override-sources-list", self._buildid]
79 args.extend(self.sources_list)60 args.extend(self.sources_list)
80 self.runSubProcess(self._sourcespath, args)61 self.runSubProcess(self._sourcespath, args)
@@ -181,26 +162,6 @@
181 self._state = DebianBuildState.UMOUNT162 self._state = DebianBuildState.UMOUNT
182 self.doUnmounting()163 self.doUnmounting()
183 else:164 else:
184 # Run OGRE if we need to, else run UPDATE
185 if self.ogre:
186 self._state = DebianBuildState.OGRE
187 self.doOgreModel()
188 elif self.sources_list is not None:
189 self._state = DebianBuildState.SOURCES
190 self.doSourcesList()
191 else:
192 self._state = DebianBuildState.UPDATE
193 self.doUpdateChroot()
194
195 def iterate_OGRE(self, success):
196 """Just finished running the ogre applicator."""
197 if success != 0:
198 if not self.alreadyfailed:
199 self._slave.chrootFail()
200 self.alreadyfailed = True
201 self._state = DebianBuildState.REAP
202 self.doReapProcesses()
203 else:
204 if self.sources_list is not None:165 if self.sources_list is not None:
205 self._state = DebianBuildState.SOURCES166 self._state = DebianBuildState.SOURCES
206 self.doSourcesList()167 self.doSourcesList()
207168
=== modified file 'lib/canonical/buildd/debian/changelog'
--- lib/canonical/buildd/debian/changelog 2010-06-10 16:52:04 +0000
+++ lib/canonical/buildd/debian/changelog 2010-06-16 14:06:25 +0000
@@ -1,3 +1,10 @@
1launchpad-buildd (63) hardy-cat; urgency=low
2
3 * Drop apply-ogre-model, since override-sources-list replaced it three years
4 ago. Also clean up extra_args parsing a bit.
5
6 -- William Grant <wgrant@ubuntu.com> Sat, 12 Jun 2010 11:33:11 +1000
7
1launchpad-buildd (62) hardy-cat; urgency=low8launchpad-buildd (62) hardy-cat; urgency=low
29
3 * Make the buildds cope with not having a sourcepackagename LP#587109 10 * Make the buildds cope with not having a sourcepackagename LP#587109
411
=== modified file 'lib/canonical/buildd/debian/rules'
--- lib/canonical/buildd/debian/rules 2010-03-10 21:23:17 +0000
+++ lib/canonical/buildd/debian/rules 2010-06-16 14:06:25 +0000
@@ -24,8 +24,8 @@
24pyfiles = debian.py slave.py binarypackage.py utils.py __init__.py \24pyfiles = debian.py slave.py binarypackage.py utils.py __init__.py \
25 sourcepackagerecipe.py translationtemplates.py25 sourcepackagerecipe.py translationtemplates.py
26slavebins = unpack-chroot mount-chroot update-debian-chroot sbuild-package \26slavebins = unpack-chroot mount-chroot update-debian-chroot sbuild-package \
27scan-for-processes umount-chroot remove-build apply-ogre-model \27 scan-for-processes umount-chroot remove-build override-sources-list \
28override-sources-list buildrecipe generate-translation-templates28 buildrecipe generate-translation-templates
2929
30BUILDDUID=6550030BUILDDUID=65500
31BUILDDGID=6550031BUILDDGID=65500
3232
=== modified file 'lib/canonical/buildd/debian/upgrade-config'
--- lib/canonical/buildd/debian/upgrade-config 2010-04-01 03:47:51 +0000
+++ lib/canonical/buildd/debian/upgrade-config 2010-06-16 14:06:25 +0000
@@ -92,6 +92,15 @@
92 'generatepath = /usr/share/launchpad-buildd/slavebin/generate-translation-templates\n'92 'generatepath = /usr/share/launchpad-buildd/slavebin/generate-translation-templates\n'
93 'resultarchive = translation-templates.tar.gz\n')93 'resultarchive = translation-templates.tar.gz\n')
9494
95def upgrade_to_63():
96 print "Upgrading %s to version 63" % conf_file
97 subprocess.call(["mv", conf_file, conf_file+"-prev63~"])
98 in_file = open(conf_file+"-prev63~", "r")
99 out_file = open(conf_file, "w")
100 for line in in_file:
101 if not line.startswith('ogrepath'):
102 out_file.write(line)
103
95104
96if __name__ == "__main__":105if __name__ == "__main__":
97 if old_version.find("~") > 0:106 if old_version.find("~") > 0:
@@ -108,4 +117,6 @@
108 upgrade_to_58()117 upgrade_to_58()
109 if int(old_version) < 59:118 if int(old_version) < 59:
110 upgrade_to_59()119 upgrade_to_59()
120 if int(old_version) < 63:
121 upgrade_to_63()
111122
112123
=== modified file 'lib/canonical/buildd/template-buildd-slave.conf'
--- lib/canonical/buildd/template-buildd-slave.conf 2010-03-12 08:32:39 +0000
+++ lib/canonical/buildd/template-buildd-slave.conf 2010-06-16 14:06:25 +0000
@@ -18,7 +18,6 @@
18[debianmanager]18[debianmanager]
19updatepath = /usr/share/launchpad-buildd/slavebin/update-debian-chroot19updatepath = /usr/share/launchpad-buildd/slavebin/update-debian-chroot
20processscanpath = /usr/share/launchpad-buildd/slavebin/scan-for-processes20processscanpath = /usr/share/launchpad-buildd/slavebin/scan-for-processes
21ogrepath = /usr/share/launchpad-buildd/slavebin/apply-ogre-model
22sourcespath = /usr/share/launchpad-buildd/slavebin/override-sources-list21sourcespath = /usr/share/launchpad-buildd/slavebin/override-sources-list
2322
24[binarypackagemanager]23[binarypackagemanager]
2524
=== modified file 'lib/canonical/buildd/tests/buildd-slave-test.conf'
--- lib/canonical/buildd/tests/buildd-slave-test.conf 2007-06-05 00:53:06 +0000
+++ lib/canonical/buildd/tests/buildd-slave-test.conf 2010-06-16 14:06:25 +0000
@@ -17,5 +17,4 @@
17sbuildargs = -dautobuild --nolog --batch17sbuildargs = -dautobuild --nolog --batch
18updatepath = /var/tmp/buildd/slavebin/update-debian-chroot18updatepath = /var/tmp/buildd/slavebin/update-debian-chroot
19processscanpath = /var/tmp/buildd/slavebin/scan-for-processes19processscanpath = /var/tmp/buildd/slavebin/scan-for-processes
20ogrepath = /var/tmp/buildd/slavebin/apply-ogre-model
21sourcespath = /usr/share/launchpad-buildd/slavebin/override-sources-list20sourcespath = /usr/share/launchpad-buildd/slavebin/override-sources-list
2221
=== modified file 'lib/canonical/launchpad/templates/launchpad-form.pt'
--- lib/canonical/launchpad/templates/launchpad-form.pt 2010-03-15 16:58:49 +0000
+++ lib/canonical/launchpad/templates/launchpad-form.pt 2010-06-16 14:06:25 +0000
@@ -171,9 +171,12 @@
171 </td>171 </td>
172 </tr>172 </tr>
173 </tal:is-visible>173 </tal:is-visible>
174 <tal:not-visible condition="not: widget/visible">174 <tal:not-visible
175 <tr>175 condition="not: widget/visible">
176 <td tal:content="structure widget/hidden" />176 <tr
177 tal:define="markup widget/hidden"
178 tal:condition="markup">
179 <td tal:content="structure markup" />
177 </tr>180 </tr>
178 </tal:not-visible>181 </tal:not-visible>
179 </metal:macro>182 </metal:macro>
180183
=== modified file 'lib/canonical/widgets/product.py'
--- lib/canonical/widgets/product.py 2009-07-21 03:56:03 +0000
+++ lib/canonical/widgets/product.py 2010-06-16 14:06:25 +0000
@@ -408,7 +408,7 @@
408408
409class GhostWidget(TextWidget):409class GhostWidget(TextWidget):
410 """A simple widget that has no HTML."""410 """A simple widget that has no HTML."""
411411 visible = False
412 # This suppresses the stuff above the widget.412 # This suppresses the stuff above the widget.
413 display_label = False413 display_label = False
414 # This suppresses the stuff underneath the widget.414 # This suppresses the stuff underneath the widget.
@@ -418,3 +418,5 @@
418 def __call__(self):418 def __call__(self):
419 """See `SimpleInputWidget`."""419 """See `SimpleInputWidget`."""
420 return ''420 return ''
421
422 hidden = __call__
421423
=== modified file 'lib/lp/buildmaster/configure.zcml'
--- lib/lp/buildmaster/configure.zcml 2010-05-20 13:12:04 +0000
+++ lib/lp/buildmaster/configure.zcml 2010-06-16 14:06:25 +0000
@@ -75,6 +75,12 @@
75 <allow75 <allow
76 interface="lp.buildmaster.interfaces.packagebuild.IPackageBuildSource" />76 interface="lp.buildmaster.interfaces.packagebuild.IPackageBuildSource" />
77 </securedutility>77 </securedutility>
78 <securedutility
79 class="lp.buildmaster.model.packagebuild.PackageBuildSet"
80 provides="lp.buildmaster.interfaces.packagebuild.IPackageBuildSet">
81 <allow
82 interface="lp.buildmaster.interfaces.packagebuild.IPackageBuildSet" />
83 </securedutility>
7884
79 <!-- BuildQueue -->85 <!-- BuildQueue -->
80 <class86 <class
8187
=== modified file 'lib/lp/buildmaster/interfaces/packagebuild.py'
--- lib/lp/buildmaster/interfaces/packagebuild.py 2010-05-21 09:42:21 +0000
+++ lib/lp/buildmaster/interfaces/packagebuild.py 2010-06-16 14:06:25 +0000
@@ -6,6 +6,7 @@
6__all__ = [6__all__ = [
7 'IPackageBuild',7 'IPackageBuild',
8 'IPackageBuildSource',8 'IPackageBuildSource',
9 'IPackageBuildSet',
9 ]10 ]
1011
1112
@@ -16,6 +17,7 @@
1617
17from canonical.launchpad import _18from canonical.launchpad import _
18from canonical.launchpad.interfaces.librarian import ILibraryFileAlias19from canonical.launchpad.interfaces.librarian import ILibraryFileAlias
20from lp.buildmaster.interfaces.buildbase import BuildStatus
19from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob21from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
20from lp.registry.interfaces.distribution import IDistribution22from lp.registry.interfaces.distribution import IDistribution
21from lp.registry.interfaces.distroseries import IDistroSeries23from lp.registry.interfaces.distroseries import IDistroSeries
@@ -162,10 +164,33 @@
162class IPackageBuildSource(Interface):164class IPackageBuildSource(Interface):
163 """A utility of this interface used to create _things_."""165 """A utility of this interface used to create _things_."""
164166
165 def new(archive, pocket, dependencies=None):167 def new(job_type, virtualized, archive, pocket, processor=None,
168 status=BuildStatus.NEEDSBUILD, dependencies=None):
166 """Create a new `IPackageBuild`.169 """Create a new `IPackageBuild`.
167170
171 :param job_type: A `BuildFarmJobType` item.
172 :param virtualized: A boolean indicating whether this build was
173 virtualized.
168 :param archive: An `IArchive`.174 :param archive: An `IArchive`.
169 :param pocket: An item of `PackagePublishingPocket`.175 :param pocket: An item of `PackagePublishingPocket`.
176 :param processor: An `IProcessor` required to run this build farm
177 job. Default is None (processor-independent).
178 :param status: A `BuildStatus` item defaulting to NEEDSBUILD.
170 :param dependencies: An optional debian-like dependency line.179 :param dependencies: An optional debian-like dependency line.
171 """180 """
181
182
183class IPackageBuildSet(Interface):
184 """A utility representing a set of package builds."""
185
186 def getBuildsForArchive(archive, status=None, pocket=None):
187 """Return package build records targeted to a given IArchive.
188
189 :param archive: The archive for which builds will be returned.
190 :param status: If status is provided, only builders with that
191 status will be returned.
192 :param pocket: If pocket is provided only builds for that pocket
193 will be returned.
194 :return: a `ResultSet` representing the requested package builds.
195 """
196
172197
=== modified file 'lib/lp/buildmaster/model/packagebuild.py'
--- lib/lp/buildmaster/model/packagebuild.py 2010-06-07 10:43:01 +0000
+++ lib/lp/buildmaster/model/packagebuild.py 2010-06-16 14:06:25 +0000
@@ -5,12 +5,14 @@
5__all__ = [5__all__ = [
6 'PackageBuild',6 'PackageBuild',
7 'PackageBuildDerived',7 'PackageBuildDerived',
8 'PackageBuildSet',
8 ]9 ]
910
1011
11from lazr.delegates import delegates12from lazr.delegates import delegates
1213
13from storm.locals import Int, Reference, Storm, Unicode14from storm.locals import Int, Reference, Storm, Unicode
15from storm.expr import Desc
1416
15from zope.component import getUtility17from zope.component import getUtility
16from zope.interface import classProvides, implements18from zope.interface import classProvides, implements
@@ -19,13 +21,16 @@
19from canonical.launchpad.browser.librarian import (21from canonical.launchpad.browser.librarian import (
20 ProxiedLibraryFileAlias)22 ProxiedLibraryFileAlias)
21from canonical.launchpad.interfaces.lpstorm import IMasterStore23from canonical.launchpad.interfaces.lpstorm import IMasterStore
24from canonical.launchpad.webapp.interfaces import (
25 IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
2226
23from lp.buildmaster.interfaces.buildbase import BuildStatus27from lp.buildmaster.interfaces.buildbase import BuildStatus
24from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource28from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource
25from lp.buildmaster.interfaces.packagebuild import (29from lp.buildmaster.interfaces.packagebuild import (
26 IPackageBuild, IPackageBuildSource)30 IPackageBuild, IPackageBuildSet, IPackageBuildSource)
27from lp.buildmaster.model.buildbase import handle_status_for_build, BuildBase31from lp.buildmaster.model.buildbase import handle_status_for_build, BuildBase
28from lp.buildmaster.model.buildfarmjob import BuildFarmJobDerived32from lp.buildmaster.model.buildfarmjob import (
33 BuildFarmJob, BuildFarmJobDerived)
29from lp.registry.interfaces.pocket import PackagePublishingPocket34from lp.registry.interfaces.pocket import PackagePublishingPocket
30from lp.soyuz.adapters.archivedependencies import (35from lp.soyuz.adapters.archivedependencies import (
31 default_component_dependency_name)36 default_component_dependency_name)
@@ -214,3 +219,41 @@
214 def _handleStatus_GIVENBACK(self, librarian, slave_status, logger):219 def _handleStatus_GIVENBACK(self, librarian, slave_status, logger):
215 return BuildBase._handleStatus_GIVENBACK(220 return BuildBase._handleStatus_GIVENBACK(
216 self, librarian, slave_status, logger)221 self, librarian, slave_status, logger)
222
223
224class PackageBuildSet:
225 implements(IPackageBuildSet)
226
227 def getBuildsForArchive(self, archive, status=None, pocket=None):
228 """See `IPackageBuildSet`."""
229
230 extra_exprs = []
231
232 if status is not None:
233 extra_exprs.append(BuildFarmJob.status == status)
234
235 if pocket:
236 extra_exprs.append(PackageBuild.pocket == pocket)
237
238 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
239 result_set = store.find(PackageBuild,
240 PackageBuild.archive == archive,
241 PackageBuild.build_farm_job == BuildFarmJob.id,
242 *extra_exprs)
243
244 # When we have a set of builds that may include pending or
245 # superseded builds, we order by -date_created (as we won't
246 # always have a date_finished). Otherwise we can order by
247 # -date_finished.
248 unfinished_states = [
249 BuildStatus.NEEDSBUILD,
250 BuildStatus.BUILDING,
251 BuildStatus.SUPERSEDED]
252 if status is None or status in unfinished_states:
253 result_set.order_by(
254 Desc(BuildFarmJob.date_created), BuildFarmJob.id)
255 else:
256 result_set.order_by(
257 Desc(BuildFarmJob.date_finished), BuildFarmJob.id)
258
259 return result_set
217260
=== modified file 'lib/lp/buildmaster/tests/test_packagebuild.py'
--- lib/lp/buildmaster/tests/test_packagebuild.py 2010-05-21 12:48:44 +0000
+++ lib/lp/buildmaster/tests/test_packagebuild.py 2010-06-16 14:06:25 +0000
@@ -15,9 +15,10 @@
1515
16from canonical.testing.layers import LaunchpadFunctionalLayer16from canonical.testing.layers import LaunchpadFunctionalLayer
1717
18from lp.buildmaster.interfaces.buildbase import BuildStatus
18from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType19from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType
19from lp.buildmaster.interfaces.packagebuild import (20from lp.buildmaster.interfaces.packagebuild import (
20 IPackageBuild, IPackageBuildSource)21 IPackageBuild, IPackageBuildSet, IPackageBuildSource)
21from lp.buildmaster.model.packagebuild import PackageBuild22from lp.buildmaster.model.packagebuild import PackageBuild
22from lp.buildmaster.tests.test_buildbase import TestBuildBaseMixin23from lp.buildmaster.tests.test_buildbase import TestBuildBaseMixin
23from lp.registry.interfaces.pocket import PackagePublishingPocket24from lp.registry.interfaces.pocket import PackagePublishingPocket
@@ -31,15 +32,16 @@
31 only classes deriving from PackageBuild should be used.32 only classes deriving from PackageBuild should be used.
32 """33 """
3334
34 def makePackageBuild(self, archive=None):35 def makePackageBuild(
36 self, archive=None, job_type=BuildFarmJobType.PACKAGEBUILD,
37 status=BuildStatus.NEEDSBUILD,
38 pocket=PackagePublishingPocket.RELEASE):
35 if archive is None:39 if archive is None:
36 archive = self.factory.makeArchive()40 archive = self.factory.makeArchive()
3741
38 return getUtility(IPackageBuildSource).new(42 return getUtility(IPackageBuildSource).new(
39 job_type=BuildFarmJobType.PACKAGEBUILD,43 job_type=job_type, virtualized=True, archive=archive,
40 virtualized=True,44 status=status, pocket=pocket)
41 archive=archive,
42 pocket=PackagePublishingPocket.RELEASE)
4345
4446
45class TestBuildBaseMethods(TestPackageBuildBase, TestBuildBaseMixin):47class TestBuildBaseMethods(TestPackageBuildBase, TestBuildBaseMixin):
@@ -176,5 +178,46 @@
176 u'My deps', self.package_build.dependencies)178 u'My deps', self.package_build.dependencies)
177179
178180
181class TestPackageBuildSet(TestPackageBuildBase):
182
183 layer = LaunchpadFunctionalLayer
184
185 def setUp(self):
186 super(TestPackageBuildSet, self).setUp()
187 person = self.factory.makePerson()
188 self.archive = self.factory.makeArchive(owner=person)
189 self.package_builds = []
190 self.package_builds.append(
191 self.makePackageBuild(archive=self.archive,
192 pocket=PackagePublishingPocket.UPDATES))
193 self.package_builds.append(
194 self.makePackageBuild(archive=self.archive,
195 status=BuildStatus.BUILDING))
196 self.package_build_set = getUtility(IPackageBuildSet)
197
198 def test_getBuildsForArchive_all(self):
199 # The default call without arguments returns all builds for the
200 # archive.
201 self.assertContentEqual(
202 self.package_builds, self.package_build_set.getBuildsForArchive(
203 self.archive))
204
205 def test_getBuildsForArchive_by_status(self):
206 # If the status arg is used, the results will be filtered by
207 # status.
208 self.assertContentEqual(
209 self.package_builds[1:],
210 self.package_build_set.getBuildsForArchive(
211 self.archive, status=BuildStatus.BUILDING))
212
213 def test_getBuildsForArchive_by_pocket(self):
214 # If the pocket arg is used, the results will be filtered by
215 # pocket.
216 self.assertContentEqual(
217 self.package_builds[:1],
218 self.package_build_set.getBuildsForArchive(
219 self.archive, pocket=PackagePublishingPocket.UPDATES))
220
221
179def test_suite():222def test_suite():
180 return unittest.TestLoader().loadTestsFromName(__name__)223 return unittest.TestLoader().loadTestsFromName(__name__)
181224
=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
--- lib/lp/code/browser/sourcepackagerecipe.py 2010-06-12 13:39:48 +0000
+++ lib/lp/code/browser/sourcepackagerecipe.py 2010-06-16 14:06:25 +0000
@@ -37,9 +37,11 @@
37 LaunchpadView, Link, Navigation, NavigationMenu, stepthrough)37 LaunchpadView, Link, Navigation, NavigationMenu, stepthrough)
38from canonical.launchpad.webapp.authorization import check_permission38from canonical.launchpad.webapp.authorization import check_permission
39from canonical.launchpad.webapp.breadcrumb import Breadcrumb39from canonical.launchpad.webapp.breadcrumb import Breadcrumb
40from canonical.launchpad.webapp.sorting import sorted_dotted_numbers
40from canonical.widgets.itemswidgets import LabeledMultiCheckBoxWidget41from canonical.widgets.itemswidgets import LabeledMultiCheckBoxWidget
41from lp.buildmaster.interfaces.buildbase import BuildStatus42from lp.buildmaster.interfaces.buildbase import BuildStatus
42from lp.code.errors import ForbiddenInstruction43from lp.code.errors import ForbiddenInstruction
44from lp.code.interfaces.branch import NoSuchBranch
43from lp.code.interfaces.sourcepackagerecipe import (45from lp.code.interfaces.sourcepackagerecipe import (
44 ISourcePackageRecipe, ISourcePackageRecipeSource, MINIMAL_RECIPE_TEXT)46 ISourcePackageRecipe, ISourcePackageRecipeSource, MINIMAL_RECIPE_TEXT)
45from lp.code.interfaces.sourcepackagerecipebuild import (47from lp.code.interfaces.sourcepackagerecipebuild import (
@@ -170,9 +172,12 @@
170 ppas = getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user)172 ppas = getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user)
171 supported_distros = [ppa.distribution for ppa in ppas]173 supported_distros = [ppa.distribution for ppa in ppas]
172 dsset = getUtility(IDistroSeriesSet).search()174 dsset = getUtility(IDistroSeriesSet).search()
173 terms = [SimpleTerm(distro, distro.id, distro.displayname)175 terms = sorted_dotted_numbers(
174 for distro in dsset if (176 [SimpleTerm(distro, distro.id, distro.displayname)
175 distro.active and distro.distribution in supported_distros)]177 for distro in dsset if (
178 distro.active and distro.distribution in supported_distros)],
179 key=lambda term: term.value.version)
180 terms.reverse()
176 return SimpleVocabulary(terms)181 return SimpleVocabulary(terms)
177182
178def target_ppas_vocabulary(context):183def target_ppas_vocabulary(context):
@@ -375,6 +380,10 @@
375 'recipe_text',380 'recipe_text',
376 'The bzr-builder instruction "run" is not permitted here.')381 'The bzr-builder instruction "run" is not permitted here.')
377 return382 return
383 except NoSuchBranch, e:
384 self.setFieldError(
385 'recipe_text', '%s is not a branch on Launchpad.' % e.name)
386 return
378387
379 self.next_url = canonical_url(source_package_recipe)388 self.next_url = canonical_url(source_package_recipe)
380389
381390
=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-06-14 20:14:15 +0000
+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-06-16 14:06:25 +0000
@@ -42,7 +42,7 @@
42 self.ppa = self.factory.makeArchive(42 self.ppa = self.factory.makeArchive(
43 displayname='Secret PPA', owner=self.chef, name='ppa')43 displayname='Secret PPA', owner=self.chef, name='ppa')
44 self.squirrel = self.factory.makeDistroSeries(44 self.squirrel = self.factory.makeDistroSeries(
45 displayname='Secret Squirrel', name='secret',45 displayname='Secret Squirrel', name='secret', version='100.04',
46 distribution=self.ppa.distribution)46 distribution=self.ppa.distribution)
47 self.squirrel.nominatedarchindep = self.squirrel.newArch(47 self.squirrel.nominatedarchindep = self.squirrel.newArch(
48 'i386', ProcessorFamily.get(1), False, self.chef,48 'i386', ProcessorFamily.get(1), False, self.chef,
@@ -65,6 +65,12 @@
65 return extract_text(find_main_content(browser.contents))65 return extract_text(find_main_content(browser.contents))
6666
6767
68def get_message_text(browser, index):
69 """Return the text of a message, specified by index."""
70 tags = find_tags_by_class(browser.contents, 'message')[index]
71 return extract_text(tags)
72
73
68class TestSourcePackageRecipeAddView(TestCaseForRecipe):74class TestSourcePackageRecipeAddView(TestCaseForRecipe):
6975
70 layer = DatabaseFunctionalLayer76 layer = DatabaseFunctionalLayer
@@ -149,7 +155,7 @@
149 browser.getControl('Create Recipe').click()155 browser.getControl('Create Recipe').click()
150156
151 self.assertEqual(157 self.assertEqual(
152 extract_text(find_tags_by_class(browser.contents, 'message')[1]),158 get_message_text(browser, 1),
153 'The bzr-builder instruction "run" is not permitted here.')159 'The bzr-builder instruction "run" is not permitted here.')
154160
155 def test_create_new_recipe_empty_name(self):161 def test_create_new_recipe_empty_name(self):
@@ -169,12 +175,29 @@
169 browser.getControl('Create Recipe').click()175 browser.getControl('Create Recipe').click()
170176
171 self.assertEqual(177 self.assertEqual(
172 extract_text(find_tags_by_class(browser.contents, 'message')[1]),178 get_message_text(browser, 1), 'Required input is missing.')
173 'Required input is missing.')179
180 def createRecipe(self, recipe_text, branch=None):
181 if branch is None:
182 product = self.factory.makeProduct(
183 name='ratatouille', displayname='Ratatouille')
184 branch = self.factory.makeBranch(
185 owner=self.chef, product=product, name='veggies')
186
187 # A new recipe can be created from the branch page.
188 browser = self.getUserBrowser(canonical_url(branch), user=self.chef)
189 browser.getLink('Create packaging recipe').click()
190
191 browser.getControl(name='field.name').value = 'daily'
192 browser.getControl('Description').value = 'Make some food!'
193 browser.getControl('Recipe text').value = recipe_text
194 browser.getControl('Create Recipe').click()
195 return browser
174196
175 def test_create_recipe_bad_text(self):197 def test_create_recipe_bad_text(self):
176 # If a user tries to create source package recipe with bad text, they198 # If a user tries to create source package recipe with bad text, they
177 # should get an error.199 # should get an error.
200<<<<<<< TREE
178 branch = self.makeBranch()201 branch = self.makeBranch()
179202
180 # A new recipe can be created from the branch page.203 # A new recipe can be created from the branch page.
@@ -186,10 +209,14 @@
186 browser.getControl('Recipe text').value = 'Foo bar baz'209 browser.getControl('Recipe text').value = 'Foo bar baz'
187 browser.getControl('Create Recipe').click()210 browser.getControl('Create Recipe').click()
188211
212=======
213 browser = self.createRecipe('Foo bar baz')
214>>>>>>> MERGE-SOURCE
189 self.assertEqual(215 self.assertEqual(
190 extract_text(find_tags_by_class(browser.contents, 'message')[1]),216 get_message_text(browser, 1),
191 'The recipe text is not a valid bzr-builder recipe.')217 'The recipe text is not a valid bzr-builder recipe.')
192218
219<<<<<<< TREE
193 def test_create_recipe_no_distroseries(self):220 def test_create_recipe_no_distroseries(self):
194 browser = self.getViewBrowser(self.makeBranch(), '+new-recipe')221 browser = self.getViewBrowser(self.makeBranch(), '+new-recipe')
195 browser.getControl(name='field.name').value = 'daily'222 browser.getControl(name='field.name').value = 'daily'
@@ -201,6 +228,28 @@
201 extract_text(find_tags_by_class(browser.contents, 'message')[1]),228 extract_text(find_tags_by_class(browser.contents, 'message')[1]),
202 'You must specify at least one series for daily builds.')229 'You must specify at least one series for daily builds.')
203230
231=======
232 def test_create_recipe_bad_base_branch(self):
233 # If a user tries to create source package recipe with a bad base
234 # branch location, they should get an error.
235 browser = self.createRecipe(MINIMAL_RECIPE_TEXT % 'foo')
236 self.assertEqual(
237 get_message_text(browser, 1), 'foo is not a branch on Launchpad.')
238
239 def test_create_recipe_bad_instruction_branch(self):
240 # If a user tries to create source package recipe with a bad
241 # instruction branch location, they should get an error.
242 product = self.factory.makeProduct(
243 name='ratatouille', displayname='Ratatouille')
244 branch = self.factory.makeBranch(
245 owner=self.chef, product=product, name='veggies')
246 recipe = MINIMAL_RECIPE_TEXT % branch.bzr_identity
247 recipe += 'nest packaging foo debian'
248 browser = self.createRecipe(recipe, branch)
249 self.assertEqual(
250 get_message_text(browser, 1), 'foo is not a branch on Launchpad.')
251
252>>>>>>> MERGE-SOURCE
204 def test_create_dupe_recipe(self):253 def test_create_dupe_recipe(self):
205 # You shouldn't be able to create a duplicate recipe owned by the same254 # You shouldn't be able to create a duplicate recipe owned by the same
206 # person with the same name.255 # person with the same name.
@@ -221,7 +270,7 @@
221 browser.getControl('Create Recipe').click()270 browser.getControl('Create Recipe').click()
222271
223 self.assertEqual(272 self.assertEqual(
224 extract_text(find_tags_by_class(browser.contents, 'message')[1]),273 get_message_text(browser, 1),
225 'There is already a recipe owned by Master Chef with this name.')274 'There is already a recipe owned by Master Chef with this name.')
226275
227276
@@ -516,8 +565,8 @@
516 Secret PPA (chef/ppa)565 Secret PPA (chef/ppa)
517 Distribution series:566 Distribution series:
518 Secret Squirrel567 Secret Squirrel
568 Hoary
519 Warty569 Warty
520 Hoary
521 or570 or
522 Cancel""")571 Cancel""")
523 main_text = self.getMainText(recipe, '+request-builds')572 main_text = self.getMainText(recipe, '+request-builds')
524573
=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
--- lib/lp/code/model/sourcepackagerecipedata.py 2010-06-11 04:28:38 +0000
+++ lib/lp/code/model/sourcepackagerecipedata.py 2010-06-16 14:06:25 +0000
@@ -211,6 +211,8 @@
211 line_number += 1211 line_number += 1
212 comment = None212 comment = None
213 db_branch = branch_map[instruction.recipe_branch.url]213 db_branch = branch_map[instruction.recipe_branch.url]
214 if db_branch is None:
215 raise NoSuchBranch(instruction.recipe_branch.url)
214 insn = _SourcePackageRecipeDataInstruction(216 insn = _SourcePackageRecipeDataInstruction(
215 instruction.recipe_branch.name, type, comment,217 instruction.recipe_branch.name, type, comment,
216 line_number, db_branch, instruction.recipe_branch.revspec,218 line_number, db_branch, instruction.recipe_branch.revspec,
217219
=== modified file 'lib/lp/registry/doc/product-widgets.txt'
--- lib/lp/registry/doc/product-widgets.txt 2009-08-13 19:36:01 +0000
+++ lib/lp/registry/doc/product-widgets.txt 2010-06-16 14:06:25 +0000
@@ -216,7 +216,6 @@
216216
217A custom widget is used to display a link to the license policy.217A custom widget is used to display a link to the license policy.
218218
219 >>> from canonical.launchpad.interfaces import License
220 >>> from canonical.widgets.product import LicenseWidget219 >>> from canonical.widgets.product import LicenseWidget
221220
222 >>> form = {'field.licenses': []}221 >>> form = {'field.licenses': []}
@@ -414,7 +413,7 @@
414 >>> print_checked_items(license_widget(), links=True)413 >>> print_checked_items(license_widget(), links=True)
415 [ ] Apache License ... <http://www.opensource.org/licenses/apache2.0.php>414 [ ] Apache License ... <http://www.opensource.org/licenses/apache2.0.php>
416 [ ] GNU LGPL v2.1 ... <http://www.opensource.org/licenses/lgpl-2.1.php>415 [ ] GNU LGPL v2.1 ... <http://www.opensource.org/licenses/lgpl-2.1.php>
417 [ ] GNU Affero GPL v3 ... <http://www.opensource.org/licenses/agpl-v3.html>416 [ ] GNU Affero GPL v3 ... <http://www.opensource.org/licenses/agpl-v3...>
418 ...417 ...
419418
420But not all of them.419But not all of them.
@@ -425,3 +424,47 @@
425 [ ] I don't know yet424 [ ] I don't know yet
426 [ ] Other/Proprietary425 [ ] Other/Proprietary
427 [ ] Other/Open Source426 [ ] Other/Open Source
427
428
429GhostWidget
430-----------
431
432The GhostWidget is used to suppress the markup of a field in cases where
433another mechanism is used to insert the field's markup into the page. Some
434widget for example generate the markup for subordinate fields, but they
435do not manage the field itself. The LicenseWidget widget for example
436generates the markup for the license_info field; the view must use a
437GhostWidget to suppress the markup.
438
439 >>> from canonical.widgets.product import GhostWidget
440
441 >>> license_info = IProduct['license_info'].bind(firefox)
442 >>> ghost_widget = GhostWidget(license_info, LaunchpadTestRequest())
443 >>> ghost_widget.visible
444 False
445 >>> ghost_widget.hidden()
446 ''
447 >>> ghost_widget()
448 ''
449
450Launchpad form macros do not generate table rows for the GhostWidget.
451
452 >>> from z3c.ptcompat import ViewPageTemplateFile
453 >>> from canonical.config import config
454 >>> from canonical.launchpad.webapp.launchpadform import (
455 ... custom_widget, LaunchpadFormView)
456
457 >>> class GhostWidgetView(LaunchpadFormView):
458 ... page_title = 'Test'
459 ... template = ViewPageTemplateFile(
460 ... config.root + '/lib/lp/app/templates/generic-edit.pt')
461 ... schema = IProduct
462 ... field_names = ['license_info']
463 ... custom_widget('license_info', GhostWidget)
464
465 >>> request = LaunchpadTestRequest()
466 >>> request.setPrincipal(factory.makePerson())
467 >>> view = GhostWidgetView(firefox, request)
468 >>> view.initialize()
469 >>> extract_text(find_tag_by_id(view.render(), 'launchpad-form-widgets'))
470 u''
428471
=== modified file 'lib/lp/registry/model/sourcepackage.py'
--- lib/lp/registry/model/sourcepackage.py 2010-06-09 08:26:26 +0000
+++ lib/lp/registry/model/sourcepackage.py 2010-06-16 14:06:25 +0000
@@ -508,15 +508,17 @@
508 return not self.__eq__(other)508 return not self.__eq__(other)
509509
510 def getBuildRecords(self, build_state=None, name=None, pocket=None,510 def getBuildRecords(self, build_state=None, name=None, pocket=None,
511 arch_tag=None, user=None):511 arch_tag=None, user=None, binary_only=True):
512 """See `IHasBuildRecords`"""
512 # Ignore "user", since it would not make any difference to the513 # Ignore "user", since it would not make any difference to the
513 # records returned here (private builds are only in PPA right514 # records returned here (private builds are only in PPA right
514 # now and this method only returns records for SPRs in a515 # now and this method only returns records for SPRs in a
515 # distribution).516 # distribution).
516 # We also ignore the name parameter (required as part of the517 # We also ignore the name parameter (required as part of the
517 # IHasBuildRecords interface) and use our own name.518 # IHasBuildRecords interface) and use our own name and the
519 # binary_only parameter as a source package can only have
520 # binary builds.
518521
519 """See `IHasBuildRecords`"""
520 clauseTables = ['SourcePackageRelease',522 clauseTables = ['SourcePackageRelease',
521 'SourcePackagePublishingHistory']523 'SourcePackagePublishingHistory']
522524
523525
=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml 2010-06-14 15:58:46 +0000
+++ lib/lp/soyuz/configure.zcml 2010-06-16 14:06:25 +0000
@@ -515,6 +515,11 @@
515 for="lp.soyuz.interfaces.binarypackagebuild.IBinaryPackageBuild"515 for="lp.soyuz.interfaces.binarypackagebuild.IBinaryPackageBuild"
516 factory="lp.soyuz.browser.build.BuildBreadcrumb"516 factory="lp.soyuz.browser.build.BuildBreadcrumb"
517 permission="zope.Public"/>517 permission="zope.Public"/>
518 <adapter
519 provides="lp.soyuz.interfaces.binarypackagebuild.IBinaryPackageBuild"
520 for="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJob"
521 factory="lp.soyuz.model.binarypackagebuild.get_binary_build_for_build_farm_job"
522 permission="zope.Public"/>
518523
519 <!-- BinaryPackageBuildSet -->524 <!-- BinaryPackageBuildSet -->
520525
521526
=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py 2010-06-15 14:36:47 +0000
+++ lib/lp/soyuz/interfaces/archive.py 2010-06-16 14:06:25 +0000
@@ -29,6 +29,7 @@
29 'IArchivePublic',29 'IArchivePublic',
30 'IArchiveSet',30 'IArchiveSet',
31 'IDistributionArchive',31 'IDistributionArchive',
32 'IncompatibleArguments',
32 'InsufficientUploadRights',33 'InsufficientUploadRights',
33 'InvalidComponent',34 'InvalidComponent',
34 'InvalidPocketForPartnerArchive',35 'InvalidPocketForPartnerArchive',
@@ -89,6 +90,11 @@
89 webservice_error(400) #Bad request.90 webservice_error(400) #Bad request.
9091
9192
93class IncompatibleArguments(Exception):
94 """Raised when incompatible arguments are passed to a method."""
95 webservice_error(400) # Bad request.
96
97
92class CannotSwitchPrivacy(Exception):98class CannotSwitchPrivacy(Exception):
93 """Raised when switching the privacy of an archive that has99 """Raised when switching the privacy of an archive that has
94 publishing records."""100 publishing records."""
95101
=== modified file 'lib/lp/soyuz/interfaces/buildrecords.py'
--- lib/lp/soyuz/interfaces/buildrecords.py 2009-07-17 00:26:05 +0000
+++ lib/lp/soyuz/interfaces/buildrecords.py 2010-06-16 14:06:25 +0000
@@ -5,7 +5,7 @@
55
6"""IHasBuildRecords interface.6"""IHasBuildRecords interface.
77
8Implemented by any object that can have `IBuild` records related to it.8Implemented by any object that can have `IPackageBuild` records related to it.
9"""9"""
1010
11__metaclass__ = type11__metaclass__ = type
@@ -45,7 +45,7 @@
45 @operation_returns_collection_of(Interface)45 @operation_returns_collection_of(Interface)
46 @export_read_operation()46 @export_read_operation()
47 def getBuildRecords(build_state=None, name=None, pocket=None,47 def getBuildRecords(build_state=None, name=None, pocket=None,
48 arch_tag=None, user=None):48 arch_tag=None, user=None, binary_only=True):
49 """Return build records in the context it is implemented.49 """Return build records in the context it is implemented.
5050
51 It excludes build records generated by Gina (imported from a external51 It excludes build records generated by Gina (imported from a external
@@ -66,9 +66,14 @@
66 :param user: optional `IPerson` corresponding to the user performing66 :param user: optional `IPerson` corresponding to the user performing
67 the request. It will filter out build records for which the user67 the request. It will filter out build records for which the user
68 have no 'view' permission.68 have no 'view' permission.
69 :param binary_only: optional boolean indicating whether only
70 `BinaryPackageBuild` objects should be returned, or more general
71 `PackageBuild` objects (which may include, for example,
72 `SourcePackageRecipeBuild` objects.
6973
70 :return: a result set containing `IBuild` records ordered by descending74 :return: a result set containing `IPackageBuild` records ordered by
71 `IBuild.datebuilt` except when builds are filtered by75 descending `IPackageBuild.date_finished` except when builds are
72 `BuildStatus.NEEDSBUILD`, in this case records are ordered by76 filtered by `BuildStatus.NEEDSBUILD`, in this case records
73 descending `BuildQueue.lastscore` (dispatching order).77 are ordered by descending `BuildQueue.lastscore`
78 (dispatching order).
74 """79 """
7580
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2010-06-14 12:19:10 +0000
+++ lib/lp/soyuz/model/archive.py 2010-06-16 14:06:25 +0000
@@ -34,6 +34,7 @@
34 cursor, quote, quote_like, sqlvalues, SQLBase)34 cursor, quote, quote_like, sqlvalues, SQLBase)
35from canonical.launchpad.interfaces.lpstorm import ISlaveStore35from canonical.launchpad.interfaces.lpstorm import ISlaveStore
36from lp.buildmaster.interfaces.buildbase import BuildStatus36from lp.buildmaster.interfaces.buildbase import BuildStatus
37from lp.buildmaster.interfaces.packagebuild import IPackageBuildSet
37from lp.buildmaster.model.buildfarmjob import BuildFarmJob38from lp.buildmaster.model.buildfarmjob import BuildFarmJob
38from lp.buildmaster.model.packagebuild import PackageBuild39from lp.buildmaster.model.packagebuild import PackageBuild
39from lp.services.job.interfaces.job import JobStatus40from lp.services.job.interfaces.job import JobStatus
@@ -65,7 +66,7 @@
65 ArchiveNotPrivate, ArchivePurpose, ArchiveStatus, CannotCopy,66 ArchiveNotPrivate, ArchivePurpose, ArchiveStatus, CannotCopy,
66 CannotSwitchPrivacy, CannotUploadToPPA, CannotUploadToPocket,67 CannotSwitchPrivacy, CannotUploadToPPA, CannotUploadToPocket,
67 DistroSeriesNotFound, IArchive, IArchiveSet, IDistributionArchive,68 DistroSeriesNotFound, IArchive, IArchiveSet, IDistributionArchive,
68 InsufficientUploadRights, InvalidPocketForPPA,69 IncompatibleArguments, InsufficientUploadRights, InvalidPocketForPPA,
69 InvalidPocketForPartnerArchive, InvalidComponent, IPPA,70 InvalidPocketForPartnerArchive, InvalidComponent, IPPA,
70 MAIN_ARCHIVE_PURPOSES, NoRightsForArchive, NoRightsForComponent,71 MAIN_ARCHIVE_PURPOSES, NoRightsForArchive, NoRightsForComponent,
71 NoSuchPPA, NoTokensForTeams, PocketNotFound, VersionRequiresName,72 NoSuchPPA, NoTokensForTeams, PocketNotFound, VersionRequiresName,
@@ -369,12 +370,21 @@
369 return None370 return None
370371
371 def getBuildRecords(self, build_state=None, name=None, pocket=None,372 def getBuildRecords(self, build_state=None, name=None, pocket=None,
372 arch_tag=None, user=None):373 arch_tag=None, user=None, binary_only=True):
373 """See IHasBuildRecords"""374 """See IHasBuildRecords"""
374 # Ignore "user", since anyone already accessing this archive375 # Ignore "user", since anyone already accessing this archive
375 # will implicitly have permission to see it.376 # will implicitly have permission to see it.
376 return getUtility(IBinaryPackageBuildSet).getBuildsForArchive(377
377 self, build_state, name, pocket, arch_tag)378 if binary_only:
379 return getUtility(IBinaryPackageBuildSet).getBuildsForArchive(
380 self, build_state, name, pocket, arch_tag)
381 else:
382 if arch_tag is not None or name is not None:
383 raise IncompatibleArguments(
384 "The 'arch_tag' and 'name' parameters can be used only "
385 "with binary_only=True.")
386 return getUtility(IPackageBuildSet).getBuildsForArchive(
387 self, status=build_state, pocket=pocket)
378388
379 def getPublishedSources(self, name=None, version=None, status=None,389 def getPublishedSources(self, name=None, version=None, status=None,
380 distroseries=None, pocket=None,390 distroseries=None, pocket=None,
381391
=== modified file 'lib/lp/soyuz/model/binarypackagebuild.py'
--- lib/lp/soyuz/model/binarypackagebuild.py 2010-06-09 12:13:53 +0000
+++ lib/lp/soyuz/model/binarypackagebuild.py 2010-06-16 14:06:25 +0000
@@ -65,6 +65,27 @@
65 PackageUpload, PackageUploadBuild)65 PackageUpload, PackageUploadBuild)
6666
6767
68def get_binary_build_for_build_farm_job(build_farm_job):
69 """Factory method to returning a binary for a build farm job."""
70 # No need to query the db if the build_farm_job doesn't have
71 # the correct job type.
72 if build_farm_job.job_type != BuildFarmJobType.PACKAGEBUILD:
73 return None
74
75 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
76 find_spec = (BinaryPackageBuild, PackageBuild, BuildFarmJob)
77 resulting_tuple = store.find(
78 find_spec,
79 BinaryPackageBuild.package_build == PackageBuild.id,
80 PackageBuild.build_farm_job == BuildFarmJob.id,
81 BuildFarmJob.id == build_farm_job.id).one()
82
83 if resulting_tuple is None:
84 return None
85
86 return resulting_tuple[0]
87
88
68class BinaryPackageBuild(PackageBuildDerived, SQLBase):89class BinaryPackageBuild(PackageBuildDerived, SQLBase):
69 implements(IBinaryPackageBuild)90 implements(IBinaryPackageBuild)
70 _table = 'BinaryPackageBuild'91 _table = 'BinaryPackageBuild'
7192
=== modified file 'lib/lp/soyuz/tests/test_archive.py'
--- lib/lp/soyuz/tests/test_archive.py 2010-06-14 15:58:46 +0000
+++ lib/lp/soyuz/tests/test_archive.py 2010-06-16 14:06:25 +0000
@@ -240,7 +240,7 @@
240 ubuntu_test = breezy_autotest.distribution240 ubuntu_test = breezy_autotest.distribution
241 self.series = [breezy_autotest]241 self.series = [breezy_autotest]
242 self.series.append(self.factory.makeDistroRelease(242 self.series.append(self.factory.makeDistroRelease(
243 distribution=ubuntu_test, name="foo-series"))243 distribution=ubuntu_test, name="foo-series", version='1.0'))
244244
245 self.sources = []245 self.sources = []
246 gedit_src_hist = self.publisher.getPubSource(246 gedit_src_hist = self.publisher.getPubSource(
247247
=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py'
--- lib/lp/soyuz/tests/test_binarypackagebuild.py 2010-06-09 12:07:58 +0000
+++ lib/lp/soyuz/tests/test_binarypackagebuild.py 2010-06-16 14:06:25 +0000
@@ -15,6 +15,7 @@
15from canonical.testing import LaunchpadZopelessLayer15from canonical.testing import LaunchpadZopelessLayer
16from lp.services.job.model.job import Job16from lp.services.job.model.job import Job
17from lp.buildmaster.interfaces.buildbase import BuildStatus17from lp.buildmaster.interfaces.buildbase import BuildStatus
18from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType
18from lp.buildmaster.interfaces.builder import IBuilderSet19from lp.buildmaster.interfaces.builder import IBuilderSet
19from lp.buildmaster.interfaces.buildqueue import IBuildQueue20from lp.buildmaster.interfaces.buildqueue import IBuildQueue
20from lp.buildmaster.interfaces.packagebuild import IPackageBuild21from lp.buildmaster.interfaces.packagebuild import IPackageBuild
@@ -120,6 +121,42 @@
120 self.build.build_farm_job.id),121 self.build.build_farm_job.id),
121 self.build.log_url)122 self.build.log_url)
122123
124 def test_adapt_from_build_farm_job(self):
125 # An `IBuildFarmJob` can be adapted to an IBinaryPackageBuild
126 # if it has the correct job type.
127 build_farm_job = self.build.build_farm_job
128 store = Store.of(build_farm_job)
129 store.flush()
130
131 binary_package_build = IBinaryPackageBuild(build_farm_job)
132 self.failUnlessEqual(self.build, binary_package_build)
133
134 def test_adapt_from_build_farm_job_wrong_type(self):
135 # An `IBuildFarmJob` of the wrong type results is None.
136 build_farm_job = self.build.build_farm_job
137 removeSecurityProxy(build_farm_job).job_type = (
138 BuildFarmJobType.RECIPEBRANCHBUILD)
139
140 binary_package_build = IBinaryPackageBuild(build_farm_job)
141 self.failUnlessEqual(None, binary_package_build)
142
143 def test_adapt_from_build_farm_job_prefetching(self):
144 # The package_build is prefetched for efficiency.
145 build_farm_job = self.build.build_farm_job
146
147 # We clear the cache to avoid getting cached objects where
148 # they would normally be queries.
149 store = Store.of(build_farm_job)
150 store.flush()
151 store.reset()
152
153 binary_package_build = IBinaryPackageBuild(build_farm_job)
154
155 self.assertStatementCount(
156 0, getattr, binary_package_build, "package_build")
157 self.assertStatementCount(
158 0, getattr, binary_package_build, "build_farm_job")
159
123160
124class TestBuildUpdateDependencies(TestCaseWithFactory):161class TestBuildUpdateDependencies(TestCaseWithFactory):
125162
126163
=== modified file 'lib/lp/soyuz/tests/test_hasbuildrecords.py'
--- lib/lp/soyuz/tests/test_hasbuildrecords.py 2010-05-06 10:35:17 +0000
+++ lib/lp/soyuz/tests/test_hasbuildrecords.py 2010-06-16 14:06:25 +0000
@@ -12,6 +12,11 @@
1212
13from lp.registry.model.sourcepackage import SourcePackage13from lp.registry.model.sourcepackage import SourcePackage
14from lp.buildmaster.interfaces.builder import IBuilderSet14from lp.buildmaster.interfaces.builder import IBuilderSet
15from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType
16from lp.buildmaster.interfaces.packagebuild import (
17 IPackageBuildSource)
18from lp.registry.interfaces.pocket import PackagePublishingPocket
19from lp.soyuz.interfaces.archive import IncompatibleArguments
15from lp.soyuz.interfaces.buildrecords import IHasBuildRecords20from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
16from lp.soyuz.model.processor import ProcessorFamilySet21from lp.soyuz.model.processor import ProcessorFamilySet
17from lp.soyuz.tests.test_binarypackagebuild import (22from lp.soyuz.tests.test_binarypackagebuild import (
@@ -81,6 +86,32 @@
8186
82 self.context = self.publisher.distroseries.main_archive87 self.context = self.publisher.distroseries.main_archive
8388
89 def test_binary_only_false(self):
90 # An archive can optionally return the more general
91 # package build objects.
92
93 # Until we have different IBuildFarmJob types implemented, we
94 # can only test this by creating a lone PackageBuild of a
95 # different type.
96 getUtility(IPackageBuildSource).new(
97 job_type=BuildFarmJobType.RECIPEBRANCHBUILD, virtualized=True,
98 archive=self.context, pocket=PackagePublishingPocket.RELEASE)
99
100 builds = self.context.getBuildRecords(binary_only=True)
101 self.failUnlessEqual(3, builds.count())
102
103 builds = self.context.getBuildRecords(binary_only=False)
104 self.failUnlessEqual(4, builds.count())
105
106 def test_incompatible_arguments(self):
107 # binary_only=False is incompatible with arch_tag and name.
108 self.failUnlessRaises(
109 IncompatibleArguments, self.context.getBuildRecords,
110 binary_only=False, arch_tag="anything")
111 self.failUnlessRaises(
112 IncompatibleArguments, self.context.getBuildRecords,
113 binary_only=False, name="anything")
114
84115
85class TestBuilderHasBuildRecords(TestHasBuildRecordsInterface):116class TestBuilderHasBuildRecords(TestHasBuildRecordsInterface):
86 """Test the Builder implementation of IHasBuildRecords."""117 """Test the Builder implementation of IHasBuildRecords."""
87118
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-06-13 05:56:31 +0000
+++ lib/lp/testing/factory.py 2010-06-16 14:06:25 +0000
@@ -22,8 +22,10 @@
22from email.mime.text import MIMEText22from email.mime.text import MIMEText
23from email.mime.multipart import MIMEMultipart23from email.mime.multipart import MIMEMultipart
24from itertools import count24from itertools import count
25import os.path
26from random import randint
25from StringIO import StringIO27from StringIO import StringIO
26import os.path28from threading import local
2729
28import pytz30import pytz
2931
@@ -56,7 +58,6 @@
56from canonical.launchpad.webapp.dbpolicy import MasterDatabasePolicy58from canonical.launchpad.webapp.dbpolicy import MasterDatabasePolicy
57from canonical.launchpad.webapp.interfaces import (59from canonical.launchpad.webapp.interfaces import (
58 IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)60 IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
59from canonical.uuid import generate_uuid
6061
61from lp.blueprints.interfaces.specification import (62from lp.blueprints.interfaces.specification import (
62 ISpecificationSet, SpecificationDefinitionStatus)63 ISpecificationSet, SpecificationDefinitionStatus)
@@ -213,14 +214,22 @@
213214
214 def __init__(self):215 def __init__(self):
215 # Initialise the unique identifier.216 # Initialise the unique identifier.
216 self._integer = count(1)217 self._local = local()
217218
218 def getUniqueEmailAddress(self):219 def getUniqueEmailAddress(self):
219 return "%s@example.com" % self.getUniqueString('email')220 return "%s@example.com" % self.getUniqueString('email')
220221
221 def getUniqueInteger(self):222 def getUniqueInteger(self):
222 """Return an integer unique to this factory instance."""223 """Return an integer unique to this factory instance.
223 return self._integer.next()224
225 For each thread, this will be a series of increasing numbers, but the
226 starting point will be unique per thread.
227 """
228 counter = getattr(self._local, 'integer', None)
229 if counter is None:
230 counter = count(randint(0, 1000000))
231 self._local.integer = counter
232 return counter.next()
224233
225 def getUniqueHexString(self, digits=None):234 def getUniqueHexString(self, digits=None):
226 """Return a unique hexadecimal string.235 """Return a unique hexadecimal string.
@@ -245,7 +254,7 @@
245 """254 """
246 if prefix is None:255 if prefix is None:
247 prefix = "generic-string"256 prefix = "generic-string"
248 string = "%s%s" % (prefix, generate_uuid())257 string = "%s%s" % (prefix, self.getUniqueInteger())
249 return string.replace('_', '-').lower()258 return string.replace('_', '-').lower()
250259
251 def getUniqueUnicode(self):260 def getUniqueUnicode(self):

Subscribers

People subscribed via source and target branches

to status/vote changes: