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
1=== removed file 'lib/canonical/buildd/apply-ogre-model'
2--- lib/canonical/buildd/apply-ogre-model 2009-06-30 21:06:27 +0000
3+++ lib/canonical/buildd/apply-ogre-model 1970-01-01 00:00:00 +0000
4@@ -1,39 +0,0 @@
5-#!/bin/sh
6-#
7-# Copyright 2009 Canonical Ltd. This software is licensed under the
8-# GNU Affero General Public License version 3 (see the file LICENSE).
9-#
10-# Author: Daniel Silverstone <daniel.silverstone@canonical.com>
11-
12-# Buildd Slave tool to apply the ogre model to the chroot
13-
14-# Expects build id as arg 1, makes build-id to contain the build
15-# Expects ogre component name as arg 2
16-
17-# Needs SUDO to be set to a sudo instance for passwordless access
18-# LN must be an ln instance which can take -s and -f at the same time
19-
20-SUDO=/usr/bin/sudo
21-
22-BUILDID="$1"
23-COMPONENT="$2"
24-
25-set -e
26-
27-exec 2>&1
28-
29-cd $HOME
30-cd "build-$BUILDID/chroot-autobuild/etc/apt"
31-
32-echo "Attempting OGRE for $COMPONENT in build-$BUILDID"
33-
34-if [ -e "sources.list.$COMPONENT" ]; then
35- if [ -h "sources.list" ]; then
36- $SUDO ln -sf "sources.list.$COMPONENT" sources.list
37- else
38- echo "OGRE sources.list found, but sources.list is not a symlink"
39- fi
40-else
41- echo "No OGRE sources.list found."
42-fi
43-
44
45=== modified file 'lib/canonical/buildd/binarypackage.py'
46--- lib/canonical/buildd/binarypackage.py 2010-01-14 02:55:22 +0000
47+++ lib/canonical/buildd/binarypackage.py 2010-06-16 14:06:25 +0000
48@@ -55,22 +55,12 @@
49 self._dscfile = f
50 if self._dscfile is None:
51 raise ValueError, files
52- if 'arch_indep' in extra_args:
53- self.arch_indep = extra_args['arch_indep']
54- else:
55- self.arch_indep = False
56- if 'suite' in extra_args:
57- self.suite = extra_args['suite']
58- else:
59- self.suite = False
60- if 'archive_purpose' in extra_args:
61- self.archive_purpose = extra_args['archive_purpose']
62- else:
63- self.archive_purpose = False
64- if 'build_debug_symbols' in extra_args:
65- self.build_debug_symbols = extra_args['build_debug_symbols']
66- else:
67- self.build_debug_symbols = False
68+
69+ self.archive_purpose = extra_args.get('archive_purpose')
70+ self.suite = extra_args.get('suite')
71+ self.component = extra_args['ogrecomponent']
72+ self.arch_indep = extra_args.get('arch_indep', False)
73+ self.build_debug_symbols = extra_args.get('build_debug_symbols', False)
74
75 super(BinaryPackageBuildManager, self).initiate(
76 files, chroot, extra_args)
77@@ -92,7 +82,7 @@
78 args.extend(["--purpose=" + self.archive_purpose])
79 if self.build_debug_symbols:
80 args.extend(["--build-debug-symbols"])
81- args.extend(["--comp=" + self.ogre])
82+ args.extend(["--comp=" + self.component])
83 args.extend([self._dscfile])
84 self.runSubProcess( self._sbuildpath, args )
85
86
87=== modified file 'lib/canonical/buildd/debian.py'
88--- lib/canonical/buildd/debian.py 2010-01-14 20:19:27 +0000
89+++ lib/canonical/buildd/debian.py 2010-06-16 14:06:25 +0000
90@@ -20,7 +20,6 @@
91 INIT = "INIT"
92 UNPACK = "UNPACK"
93 MOUNT = "MOUNT"
94- OGRE = "OGRE"
95 SOURCES = "SOURCES"
96 UPDATE = "UPDATE"
97 REAP = "REAP"
98@@ -35,7 +34,6 @@
99 BuildManager.__init__(self, slave, buildid)
100 self._updatepath = slave._config.get("debianmanager", "updatepath")
101 self._scanpath = slave._config.get("debianmanager", "processscanpath")
102- self._ogrepath = slave._config.get("debianmanager", "ogrepath")
103 self._sourcespath = slave._config.get("debianmanager", "sourcespath")
104 self._cachepath = slave._config.get("slave","filecache")
105 self._state = DebianBuildState.INIT
106@@ -49,32 +47,15 @@
107 def initiate(self, files, chroot, extra_args):
108 """Initiate a build with a given set of files and chroot."""
109
110- if 'ogrecomponent' in extra_args:
111- # Ubuntu refers to the concept that "main sees only main
112- # while building" etc as "The Ogre Model" (onions, layers
113- # and all). If we're given an ogre component, use it
114- self.ogre = extra_args['ogrecomponent']
115- else:
116- self.ogre = False
117- if 'archives' in extra_args and extra_args['archives']:
118- self.sources_list = extra_args['archives']
119- else:
120- self.sources_list = None
121+ self.sources_list = extra_args.get('archives')
122
123 BuildManager.initiate(self, files, chroot, extra_args)
124
125- def doOgreModel(self):
126- """Perform the ogre model activation."""
127- self.runSubProcess(self._ogrepath,
128- ["apply-ogre-model", self._buildid, self.ogre])
129-
130 def doSourcesList(self):
131 """Override apt/sources.list.
132
133 Mainly used for PPA builds.
134 """
135- # XXX cprov 2007-05-17: It 'undo' ogre-component changes.
136- # for PPAs it must be re-implemented on builddmaster side.
137 args = ["override-sources-list", self._buildid]
138 args.extend(self.sources_list)
139 self.runSubProcess(self._sourcespath, args)
140@@ -181,26 +162,6 @@
141 self._state = DebianBuildState.UMOUNT
142 self.doUnmounting()
143 else:
144- # Run OGRE if we need to, else run UPDATE
145- if self.ogre:
146- self._state = DebianBuildState.OGRE
147- self.doOgreModel()
148- elif self.sources_list is not None:
149- self._state = DebianBuildState.SOURCES
150- self.doSourcesList()
151- else:
152- self._state = DebianBuildState.UPDATE
153- self.doUpdateChroot()
154-
155- def iterate_OGRE(self, success):
156- """Just finished running the ogre applicator."""
157- if success != 0:
158- if not self.alreadyfailed:
159- self._slave.chrootFail()
160- self.alreadyfailed = True
161- self._state = DebianBuildState.REAP
162- self.doReapProcesses()
163- else:
164 if self.sources_list is not None:
165 self._state = DebianBuildState.SOURCES
166 self.doSourcesList()
167
168=== modified file 'lib/canonical/buildd/debian/changelog'
169--- lib/canonical/buildd/debian/changelog 2010-06-10 16:52:04 +0000
170+++ lib/canonical/buildd/debian/changelog 2010-06-16 14:06:25 +0000
171@@ -1,3 +1,10 @@
172+launchpad-buildd (63) hardy-cat; urgency=low
173+
174+ * Drop apply-ogre-model, since override-sources-list replaced it three years
175+ ago. Also clean up extra_args parsing a bit.
176+
177+ -- William Grant <wgrant@ubuntu.com> Sat, 12 Jun 2010 11:33:11 +1000
178+
179 launchpad-buildd (62) hardy-cat; urgency=low
180
181 * Make the buildds cope with not having a sourcepackagename LP#587109
182
183=== modified file 'lib/canonical/buildd/debian/rules'
184--- lib/canonical/buildd/debian/rules 2010-03-10 21:23:17 +0000
185+++ lib/canonical/buildd/debian/rules 2010-06-16 14:06:25 +0000
186@@ -24,8 +24,8 @@
187 pyfiles = debian.py slave.py binarypackage.py utils.py __init__.py \
188 sourcepackagerecipe.py translationtemplates.py
189 slavebins = unpack-chroot mount-chroot update-debian-chroot sbuild-package \
190-scan-for-processes umount-chroot remove-build apply-ogre-model \
191-override-sources-list buildrecipe generate-translation-templates
192+ scan-for-processes umount-chroot remove-build override-sources-list \
193+ buildrecipe generate-translation-templates
194
195 BUILDDUID=65500
196 BUILDDGID=65500
197
198=== modified file 'lib/canonical/buildd/debian/upgrade-config'
199--- lib/canonical/buildd/debian/upgrade-config 2010-04-01 03:47:51 +0000
200+++ lib/canonical/buildd/debian/upgrade-config 2010-06-16 14:06:25 +0000
201@@ -92,6 +92,15 @@
202 'generatepath = /usr/share/launchpad-buildd/slavebin/generate-translation-templates\n'
203 'resultarchive = translation-templates.tar.gz\n')
204
205+def upgrade_to_63():
206+ print "Upgrading %s to version 63" % conf_file
207+ subprocess.call(["mv", conf_file, conf_file+"-prev63~"])
208+ in_file = open(conf_file+"-prev63~", "r")
209+ out_file = open(conf_file, "w")
210+ for line in in_file:
211+ if not line.startswith('ogrepath'):
212+ out_file.write(line)
213+
214
215 if __name__ == "__main__":
216 if old_version.find("~") > 0:
217@@ -108,4 +117,6 @@
218 upgrade_to_58()
219 if int(old_version) < 59:
220 upgrade_to_59()
221+ if int(old_version) < 63:
222+ upgrade_to_63()
223
224
225=== modified file 'lib/canonical/buildd/template-buildd-slave.conf'
226--- lib/canonical/buildd/template-buildd-slave.conf 2010-03-12 08:32:39 +0000
227+++ lib/canonical/buildd/template-buildd-slave.conf 2010-06-16 14:06:25 +0000
228@@ -18,7 +18,6 @@
229 [debianmanager]
230 updatepath = /usr/share/launchpad-buildd/slavebin/update-debian-chroot
231 processscanpath = /usr/share/launchpad-buildd/slavebin/scan-for-processes
232-ogrepath = /usr/share/launchpad-buildd/slavebin/apply-ogre-model
233 sourcespath = /usr/share/launchpad-buildd/slavebin/override-sources-list
234
235 [binarypackagemanager]
236
237=== modified file 'lib/canonical/buildd/tests/buildd-slave-test.conf'
238--- lib/canonical/buildd/tests/buildd-slave-test.conf 2007-06-05 00:53:06 +0000
239+++ lib/canonical/buildd/tests/buildd-slave-test.conf 2010-06-16 14:06:25 +0000
240@@ -17,5 +17,4 @@
241 sbuildargs = -dautobuild --nolog --batch
242 updatepath = /var/tmp/buildd/slavebin/update-debian-chroot
243 processscanpath = /var/tmp/buildd/slavebin/scan-for-processes
244-ogrepath = /var/tmp/buildd/slavebin/apply-ogre-model
245 sourcespath = /usr/share/launchpad-buildd/slavebin/override-sources-list
246
247=== modified file 'lib/canonical/launchpad/templates/launchpad-form.pt'
248--- lib/canonical/launchpad/templates/launchpad-form.pt 2010-03-15 16:58:49 +0000
249+++ lib/canonical/launchpad/templates/launchpad-form.pt 2010-06-16 14:06:25 +0000
250@@ -171,9 +171,12 @@
251 </td>
252 </tr>
253 </tal:is-visible>
254- <tal:not-visible condition="not: widget/visible">
255- <tr>
256- <td tal:content="structure widget/hidden" />
257+ <tal:not-visible
258+ condition="not: widget/visible">
259+ <tr
260+ tal:define="markup widget/hidden"
261+ tal:condition="markup">
262+ <td tal:content="structure markup" />
263 </tr>
264 </tal:not-visible>
265 </metal:macro>
266
267=== modified file 'lib/canonical/widgets/product.py'
268--- lib/canonical/widgets/product.py 2009-07-21 03:56:03 +0000
269+++ lib/canonical/widgets/product.py 2010-06-16 14:06:25 +0000
270@@ -408,7 +408,7 @@
271
272 class GhostWidget(TextWidget):
273 """A simple widget that has no HTML."""
274-
275+ visible = False
276 # This suppresses the stuff above the widget.
277 display_label = False
278 # This suppresses the stuff underneath the widget.
279@@ -418,3 +418,5 @@
280 def __call__(self):
281 """See `SimpleInputWidget`."""
282 return ''
283+
284+ hidden = __call__
285
286=== modified file 'lib/lp/buildmaster/configure.zcml'
287--- lib/lp/buildmaster/configure.zcml 2010-05-20 13:12:04 +0000
288+++ lib/lp/buildmaster/configure.zcml 2010-06-16 14:06:25 +0000
289@@ -75,6 +75,12 @@
290 <allow
291 interface="lp.buildmaster.interfaces.packagebuild.IPackageBuildSource" />
292 </securedutility>
293+ <securedutility
294+ class="lp.buildmaster.model.packagebuild.PackageBuildSet"
295+ provides="lp.buildmaster.interfaces.packagebuild.IPackageBuildSet">
296+ <allow
297+ interface="lp.buildmaster.interfaces.packagebuild.IPackageBuildSet" />
298+ </securedutility>
299
300 <!-- BuildQueue -->
301 <class
302
303=== modified file 'lib/lp/buildmaster/interfaces/packagebuild.py'
304--- lib/lp/buildmaster/interfaces/packagebuild.py 2010-05-21 09:42:21 +0000
305+++ lib/lp/buildmaster/interfaces/packagebuild.py 2010-06-16 14:06:25 +0000
306@@ -6,6 +6,7 @@
307 __all__ = [
308 'IPackageBuild',
309 'IPackageBuildSource',
310+ 'IPackageBuildSet',
311 ]
312
313
314@@ -16,6 +17,7 @@
315
316 from canonical.launchpad import _
317 from canonical.launchpad.interfaces.librarian import ILibraryFileAlias
318+from lp.buildmaster.interfaces.buildbase import BuildStatus
319 from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
320 from lp.registry.interfaces.distribution import IDistribution
321 from lp.registry.interfaces.distroseries import IDistroSeries
322@@ -162,10 +164,33 @@
323 class IPackageBuildSource(Interface):
324 """A utility of this interface used to create _things_."""
325
326- def new(archive, pocket, dependencies=None):
327+ def new(job_type, virtualized, archive, pocket, processor=None,
328+ status=BuildStatus.NEEDSBUILD, dependencies=None):
329 """Create a new `IPackageBuild`.
330
331+ :param job_type: A `BuildFarmJobType` item.
332+ :param virtualized: A boolean indicating whether this build was
333+ virtualized.
334 :param archive: An `IArchive`.
335 :param pocket: An item of `PackagePublishingPocket`.
336+ :param processor: An `IProcessor` required to run this build farm
337+ job. Default is None (processor-independent).
338+ :param status: A `BuildStatus` item defaulting to NEEDSBUILD.
339 :param dependencies: An optional debian-like dependency line.
340 """
341+
342+
343+class IPackageBuildSet(Interface):
344+ """A utility representing a set of package builds."""
345+
346+ def getBuildsForArchive(archive, status=None, pocket=None):
347+ """Return package build records targeted to a given IArchive.
348+
349+ :param archive: The archive for which builds will be returned.
350+ :param status: If status is provided, only builders with that
351+ status will be returned.
352+ :param pocket: If pocket is provided only builds for that pocket
353+ will be returned.
354+ :return: a `ResultSet` representing the requested package builds.
355+ """
356+
357
358=== modified file 'lib/lp/buildmaster/model/packagebuild.py'
359--- lib/lp/buildmaster/model/packagebuild.py 2010-06-07 10:43:01 +0000
360+++ lib/lp/buildmaster/model/packagebuild.py 2010-06-16 14:06:25 +0000
361@@ -5,12 +5,14 @@
362 __all__ = [
363 'PackageBuild',
364 'PackageBuildDerived',
365+ 'PackageBuildSet',
366 ]
367
368
369 from lazr.delegates import delegates
370
371 from storm.locals import Int, Reference, Storm, Unicode
372+from storm.expr import Desc
373
374 from zope.component import getUtility
375 from zope.interface import classProvides, implements
376@@ -19,13 +21,16 @@
377 from canonical.launchpad.browser.librarian import (
378 ProxiedLibraryFileAlias)
379 from canonical.launchpad.interfaces.lpstorm import IMasterStore
380+from canonical.launchpad.webapp.interfaces import (
381+ IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
382
383 from lp.buildmaster.interfaces.buildbase import BuildStatus
384 from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource
385 from lp.buildmaster.interfaces.packagebuild import (
386- IPackageBuild, IPackageBuildSource)
387+ IPackageBuild, IPackageBuildSet, IPackageBuildSource)
388 from lp.buildmaster.model.buildbase import handle_status_for_build, BuildBase
389-from lp.buildmaster.model.buildfarmjob import BuildFarmJobDerived
390+from lp.buildmaster.model.buildfarmjob import (
391+ BuildFarmJob, BuildFarmJobDerived)
392 from lp.registry.interfaces.pocket import PackagePublishingPocket
393 from lp.soyuz.adapters.archivedependencies import (
394 default_component_dependency_name)
395@@ -214,3 +219,41 @@
396 def _handleStatus_GIVENBACK(self, librarian, slave_status, logger):
397 return BuildBase._handleStatus_GIVENBACK(
398 self, librarian, slave_status, logger)
399+
400+
401+class PackageBuildSet:
402+ implements(IPackageBuildSet)
403+
404+ def getBuildsForArchive(self, archive, status=None, pocket=None):
405+ """See `IPackageBuildSet`."""
406+
407+ extra_exprs = []
408+
409+ if status is not None:
410+ extra_exprs.append(BuildFarmJob.status == status)
411+
412+ if pocket:
413+ extra_exprs.append(PackageBuild.pocket == pocket)
414+
415+ store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
416+ result_set = store.find(PackageBuild,
417+ PackageBuild.archive == archive,
418+ PackageBuild.build_farm_job == BuildFarmJob.id,
419+ *extra_exprs)
420+
421+ # When we have a set of builds that may include pending or
422+ # superseded builds, we order by -date_created (as we won't
423+ # always have a date_finished). Otherwise we can order by
424+ # -date_finished.
425+ unfinished_states = [
426+ BuildStatus.NEEDSBUILD,
427+ BuildStatus.BUILDING,
428+ BuildStatus.SUPERSEDED]
429+ if status is None or status in unfinished_states:
430+ result_set.order_by(
431+ Desc(BuildFarmJob.date_created), BuildFarmJob.id)
432+ else:
433+ result_set.order_by(
434+ Desc(BuildFarmJob.date_finished), BuildFarmJob.id)
435+
436+ return result_set
437
438=== modified file 'lib/lp/buildmaster/tests/test_packagebuild.py'
439--- lib/lp/buildmaster/tests/test_packagebuild.py 2010-05-21 12:48:44 +0000
440+++ lib/lp/buildmaster/tests/test_packagebuild.py 2010-06-16 14:06:25 +0000
441@@ -15,9 +15,10 @@
442
443 from canonical.testing.layers import LaunchpadFunctionalLayer
444
445+from lp.buildmaster.interfaces.buildbase import BuildStatus
446 from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType
447 from lp.buildmaster.interfaces.packagebuild import (
448- IPackageBuild, IPackageBuildSource)
449+ IPackageBuild, IPackageBuildSet, IPackageBuildSource)
450 from lp.buildmaster.model.packagebuild import PackageBuild
451 from lp.buildmaster.tests.test_buildbase import TestBuildBaseMixin
452 from lp.registry.interfaces.pocket import PackagePublishingPocket
453@@ -31,15 +32,16 @@
454 only classes deriving from PackageBuild should be used.
455 """
456
457- def makePackageBuild(self, archive=None):
458+ def makePackageBuild(
459+ self, archive=None, job_type=BuildFarmJobType.PACKAGEBUILD,
460+ status=BuildStatus.NEEDSBUILD,
461+ pocket=PackagePublishingPocket.RELEASE):
462 if archive is None:
463 archive = self.factory.makeArchive()
464
465 return getUtility(IPackageBuildSource).new(
466- job_type=BuildFarmJobType.PACKAGEBUILD,
467- virtualized=True,
468- archive=archive,
469- pocket=PackagePublishingPocket.RELEASE)
470+ job_type=job_type, virtualized=True, archive=archive,
471+ status=status, pocket=pocket)
472
473
474 class TestBuildBaseMethods(TestPackageBuildBase, TestBuildBaseMixin):
475@@ -176,5 +178,46 @@
476 u'My deps', self.package_build.dependencies)
477
478
479+class TestPackageBuildSet(TestPackageBuildBase):
480+
481+ layer = LaunchpadFunctionalLayer
482+
483+ def setUp(self):
484+ super(TestPackageBuildSet, self).setUp()
485+ person = self.factory.makePerson()
486+ self.archive = self.factory.makeArchive(owner=person)
487+ self.package_builds = []
488+ self.package_builds.append(
489+ self.makePackageBuild(archive=self.archive,
490+ pocket=PackagePublishingPocket.UPDATES))
491+ self.package_builds.append(
492+ self.makePackageBuild(archive=self.archive,
493+ status=BuildStatus.BUILDING))
494+ self.package_build_set = getUtility(IPackageBuildSet)
495+
496+ def test_getBuildsForArchive_all(self):
497+ # The default call without arguments returns all builds for the
498+ # archive.
499+ self.assertContentEqual(
500+ self.package_builds, self.package_build_set.getBuildsForArchive(
501+ self.archive))
502+
503+ def test_getBuildsForArchive_by_status(self):
504+ # If the status arg is used, the results will be filtered by
505+ # status.
506+ self.assertContentEqual(
507+ self.package_builds[1:],
508+ self.package_build_set.getBuildsForArchive(
509+ self.archive, status=BuildStatus.BUILDING))
510+
511+ def test_getBuildsForArchive_by_pocket(self):
512+ # If the pocket arg is used, the results will be filtered by
513+ # pocket.
514+ self.assertContentEqual(
515+ self.package_builds[:1],
516+ self.package_build_set.getBuildsForArchive(
517+ self.archive, pocket=PackagePublishingPocket.UPDATES))
518+
519+
520 def test_suite():
521 return unittest.TestLoader().loadTestsFromName(__name__)
522
523=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
524--- lib/lp/code/browser/sourcepackagerecipe.py 2010-06-12 13:39:48 +0000
525+++ lib/lp/code/browser/sourcepackagerecipe.py 2010-06-16 14:06:25 +0000
526@@ -37,9 +37,11 @@
527 LaunchpadView, Link, Navigation, NavigationMenu, stepthrough)
528 from canonical.launchpad.webapp.authorization import check_permission
529 from canonical.launchpad.webapp.breadcrumb import Breadcrumb
530+from canonical.launchpad.webapp.sorting import sorted_dotted_numbers
531 from canonical.widgets.itemswidgets import LabeledMultiCheckBoxWidget
532 from lp.buildmaster.interfaces.buildbase import BuildStatus
533 from lp.code.errors import ForbiddenInstruction
534+from lp.code.interfaces.branch import NoSuchBranch
535 from lp.code.interfaces.sourcepackagerecipe import (
536 ISourcePackageRecipe, ISourcePackageRecipeSource, MINIMAL_RECIPE_TEXT)
537 from lp.code.interfaces.sourcepackagerecipebuild import (
538@@ -170,9 +172,12 @@
539 ppas = getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user)
540 supported_distros = [ppa.distribution for ppa in ppas]
541 dsset = getUtility(IDistroSeriesSet).search()
542- terms = [SimpleTerm(distro, distro.id, distro.displayname)
543- for distro in dsset if (
544- distro.active and distro.distribution in supported_distros)]
545+ terms = sorted_dotted_numbers(
546+ [SimpleTerm(distro, distro.id, distro.displayname)
547+ for distro in dsset if (
548+ distro.active and distro.distribution in supported_distros)],
549+ key=lambda term: term.value.version)
550+ terms.reverse()
551 return SimpleVocabulary(terms)
552
553 def target_ppas_vocabulary(context):
554@@ -375,6 +380,10 @@
555 'recipe_text',
556 'The bzr-builder instruction "run" is not permitted here.')
557 return
558+ except NoSuchBranch, e:
559+ self.setFieldError(
560+ 'recipe_text', '%s is not a branch on Launchpad.' % e.name)
561+ return
562
563 self.next_url = canonical_url(source_package_recipe)
564
565
566=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
567--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-06-14 20:14:15 +0000
568+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-06-16 14:06:25 +0000
569@@ -42,7 +42,7 @@
570 self.ppa = self.factory.makeArchive(
571 displayname='Secret PPA', owner=self.chef, name='ppa')
572 self.squirrel = self.factory.makeDistroSeries(
573- displayname='Secret Squirrel', name='secret',
574+ displayname='Secret Squirrel', name='secret', version='100.04',
575 distribution=self.ppa.distribution)
576 self.squirrel.nominatedarchindep = self.squirrel.newArch(
577 'i386', ProcessorFamily.get(1), False, self.chef,
578@@ -65,6 +65,12 @@
579 return extract_text(find_main_content(browser.contents))
580
581
582+def get_message_text(browser, index):
583+ """Return the text of a message, specified by index."""
584+ tags = find_tags_by_class(browser.contents, 'message')[index]
585+ return extract_text(tags)
586+
587+
588 class TestSourcePackageRecipeAddView(TestCaseForRecipe):
589
590 layer = DatabaseFunctionalLayer
591@@ -149,7 +155,7 @@
592 browser.getControl('Create Recipe').click()
593
594 self.assertEqual(
595- extract_text(find_tags_by_class(browser.contents, 'message')[1]),
596+ get_message_text(browser, 1),
597 'The bzr-builder instruction "run" is not permitted here.')
598
599 def test_create_new_recipe_empty_name(self):
600@@ -169,12 +175,29 @@
601 browser.getControl('Create Recipe').click()
602
603 self.assertEqual(
604- extract_text(find_tags_by_class(browser.contents, 'message')[1]),
605- 'Required input is missing.')
606+ get_message_text(browser, 1), 'Required input is missing.')
607+
608+ def createRecipe(self, recipe_text, branch=None):
609+ if branch is None:
610+ product = self.factory.makeProduct(
611+ name='ratatouille', displayname='Ratatouille')
612+ branch = self.factory.makeBranch(
613+ owner=self.chef, product=product, name='veggies')
614+
615+ # A new recipe can be created from the branch page.
616+ browser = self.getUserBrowser(canonical_url(branch), user=self.chef)
617+ browser.getLink('Create packaging recipe').click()
618+
619+ browser.getControl(name='field.name').value = 'daily'
620+ browser.getControl('Description').value = 'Make some food!'
621+ browser.getControl('Recipe text').value = recipe_text
622+ browser.getControl('Create Recipe').click()
623+ return browser
624
625 def test_create_recipe_bad_text(self):
626 # If a user tries to create source package recipe with bad text, they
627 # should get an error.
628+<<<<<<< TREE
629 branch = self.makeBranch()
630
631 # A new recipe can be created from the branch page.
632@@ -186,10 +209,14 @@
633 browser.getControl('Recipe text').value = 'Foo bar baz'
634 browser.getControl('Create Recipe').click()
635
636+=======
637+ browser = self.createRecipe('Foo bar baz')
638+>>>>>>> MERGE-SOURCE
639 self.assertEqual(
640- extract_text(find_tags_by_class(browser.contents, 'message')[1]),
641+ get_message_text(browser, 1),
642 'The recipe text is not a valid bzr-builder recipe.')
643
644+<<<<<<< TREE
645 def test_create_recipe_no_distroseries(self):
646 browser = self.getViewBrowser(self.makeBranch(), '+new-recipe')
647 browser.getControl(name='field.name').value = 'daily'
648@@ -201,6 +228,28 @@
649 extract_text(find_tags_by_class(browser.contents, 'message')[1]),
650 'You must specify at least one series for daily builds.')
651
652+=======
653+ def test_create_recipe_bad_base_branch(self):
654+ # If a user tries to create source package recipe with a bad base
655+ # branch location, they should get an error.
656+ browser = self.createRecipe(MINIMAL_RECIPE_TEXT % 'foo')
657+ self.assertEqual(
658+ get_message_text(browser, 1), 'foo is not a branch on Launchpad.')
659+
660+ def test_create_recipe_bad_instruction_branch(self):
661+ # If a user tries to create source package recipe with a bad
662+ # instruction branch location, they should get an error.
663+ product = self.factory.makeProduct(
664+ name='ratatouille', displayname='Ratatouille')
665+ branch = self.factory.makeBranch(
666+ owner=self.chef, product=product, name='veggies')
667+ recipe = MINIMAL_RECIPE_TEXT % branch.bzr_identity
668+ recipe += 'nest packaging foo debian'
669+ browser = self.createRecipe(recipe, branch)
670+ self.assertEqual(
671+ get_message_text(browser, 1), 'foo is not a branch on Launchpad.')
672+
673+>>>>>>> MERGE-SOURCE
674 def test_create_dupe_recipe(self):
675 # You shouldn't be able to create a duplicate recipe owned by the same
676 # person with the same name.
677@@ -221,7 +270,7 @@
678 browser.getControl('Create Recipe').click()
679
680 self.assertEqual(
681- extract_text(find_tags_by_class(browser.contents, 'message')[1]),
682+ get_message_text(browser, 1),
683 'There is already a recipe owned by Master Chef with this name.')
684
685
686@@ -516,8 +565,8 @@
687 Secret PPA (chef/ppa)
688 Distribution series:
689 Secret Squirrel
690+ Hoary
691 Warty
692- Hoary
693 or
694 Cancel""")
695 main_text = self.getMainText(recipe, '+request-builds')
696
697=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
698--- lib/lp/code/model/sourcepackagerecipedata.py 2010-06-11 04:28:38 +0000
699+++ lib/lp/code/model/sourcepackagerecipedata.py 2010-06-16 14:06:25 +0000
700@@ -211,6 +211,8 @@
701 line_number += 1
702 comment = None
703 db_branch = branch_map[instruction.recipe_branch.url]
704+ if db_branch is None:
705+ raise NoSuchBranch(instruction.recipe_branch.url)
706 insn = _SourcePackageRecipeDataInstruction(
707 instruction.recipe_branch.name, type, comment,
708 line_number, db_branch, instruction.recipe_branch.revspec,
709
710=== modified file 'lib/lp/registry/doc/product-widgets.txt'
711--- lib/lp/registry/doc/product-widgets.txt 2009-08-13 19:36:01 +0000
712+++ lib/lp/registry/doc/product-widgets.txt 2010-06-16 14:06:25 +0000
713@@ -216,7 +216,6 @@
714
715 A custom widget is used to display a link to the license policy.
716
717- >>> from canonical.launchpad.interfaces import License
718 >>> from canonical.widgets.product import LicenseWidget
719
720 >>> form = {'field.licenses': []}
721@@ -414,7 +413,7 @@
722 >>> print_checked_items(license_widget(), links=True)
723 [ ] Apache License ... <http://www.opensource.org/licenses/apache2.0.php>
724 [ ] GNU LGPL v2.1 ... <http://www.opensource.org/licenses/lgpl-2.1.php>
725- [ ] GNU Affero GPL v3 ... <http://www.opensource.org/licenses/agpl-v3.html>
726+ [ ] GNU Affero GPL v3 ... <http://www.opensource.org/licenses/agpl-v3...>
727 ...
728
729 But not all of them.
730@@ -425,3 +424,47 @@
731 [ ] I don't know yet
732 [ ] Other/Proprietary
733 [ ] Other/Open Source
734+
735+
736+GhostWidget
737+-----------
738+
739+The GhostWidget is used to suppress the markup of a field in cases where
740+another mechanism is used to insert the field's markup into the page. Some
741+widget for example generate the markup for subordinate fields, but they
742+do not manage the field itself. The LicenseWidget widget for example
743+generates the markup for the license_info field; the view must use a
744+GhostWidget to suppress the markup.
745+
746+ >>> from canonical.widgets.product import GhostWidget
747+
748+ >>> license_info = IProduct['license_info'].bind(firefox)
749+ >>> ghost_widget = GhostWidget(license_info, LaunchpadTestRequest())
750+ >>> ghost_widget.visible
751+ False
752+ >>> ghost_widget.hidden()
753+ ''
754+ >>> ghost_widget()
755+ ''
756+
757+Launchpad form macros do not generate table rows for the GhostWidget.
758+
759+ >>> from z3c.ptcompat import ViewPageTemplateFile
760+ >>> from canonical.config import config
761+ >>> from canonical.launchpad.webapp.launchpadform import (
762+ ... custom_widget, LaunchpadFormView)
763+
764+ >>> class GhostWidgetView(LaunchpadFormView):
765+ ... page_title = 'Test'
766+ ... template = ViewPageTemplateFile(
767+ ... config.root + '/lib/lp/app/templates/generic-edit.pt')
768+ ... schema = IProduct
769+ ... field_names = ['license_info']
770+ ... custom_widget('license_info', GhostWidget)
771+
772+ >>> request = LaunchpadTestRequest()
773+ >>> request.setPrincipal(factory.makePerson())
774+ >>> view = GhostWidgetView(firefox, request)
775+ >>> view.initialize()
776+ >>> extract_text(find_tag_by_id(view.render(), 'launchpad-form-widgets'))
777+ u''
778
779=== modified file 'lib/lp/registry/model/sourcepackage.py'
780--- lib/lp/registry/model/sourcepackage.py 2010-06-09 08:26:26 +0000
781+++ lib/lp/registry/model/sourcepackage.py 2010-06-16 14:06:25 +0000
782@@ -508,15 +508,17 @@
783 return not self.__eq__(other)
784
785 def getBuildRecords(self, build_state=None, name=None, pocket=None,
786- arch_tag=None, user=None):
787+ arch_tag=None, user=None, binary_only=True):
788+ """See `IHasBuildRecords`"""
789 # Ignore "user", since it would not make any difference to the
790 # records returned here (private builds are only in PPA right
791 # now and this method only returns records for SPRs in a
792 # distribution).
793 # We also ignore the name parameter (required as part of the
794- # IHasBuildRecords interface) and use our own name.
795+ # IHasBuildRecords interface) and use our own name and the
796+ # binary_only parameter as a source package can only have
797+ # binary builds.
798
799- """See `IHasBuildRecords`"""
800 clauseTables = ['SourcePackageRelease',
801 'SourcePackagePublishingHistory']
802
803
804=== modified file 'lib/lp/soyuz/configure.zcml'
805--- lib/lp/soyuz/configure.zcml 2010-06-14 15:58:46 +0000
806+++ lib/lp/soyuz/configure.zcml 2010-06-16 14:06:25 +0000
807@@ -515,6 +515,11 @@
808 for="lp.soyuz.interfaces.binarypackagebuild.IBinaryPackageBuild"
809 factory="lp.soyuz.browser.build.BuildBreadcrumb"
810 permission="zope.Public"/>
811+ <adapter
812+ provides="lp.soyuz.interfaces.binarypackagebuild.IBinaryPackageBuild"
813+ for="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJob"
814+ factory="lp.soyuz.model.binarypackagebuild.get_binary_build_for_build_farm_job"
815+ permission="zope.Public"/>
816
817 <!-- BinaryPackageBuildSet -->
818
819
820=== modified file 'lib/lp/soyuz/interfaces/archive.py'
821--- lib/lp/soyuz/interfaces/archive.py 2010-06-15 14:36:47 +0000
822+++ lib/lp/soyuz/interfaces/archive.py 2010-06-16 14:06:25 +0000
823@@ -29,6 +29,7 @@
824 'IArchivePublic',
825 'IArchiveSet',
826 'IDistributionArchive',
827+ 'IncompatibleArguments',
828 'InsufficientUploadRights',
829 'InvalidComponent',
830 'InvalidPocketForPartnerArchive',
831@@ -89,6 +90,11 @@
832 webservice_error(400) #Bad request.
833
834
835+class IncompatibleArguments(Exception):
836+ """Raised when incompatible arguments are passed to a method."""
837+ webservice_error(400) # Bad request.
838+
839+
840 class CannotSwitchPrivacy(Exception):
841 """Raised when switching the privacy of an archive that has
842 publishing records."""
843
844=== modified file 'lib/lp/soyuz/interfaces/buildrecords.py'
845--- lib/lp/soyuz/interfaces/buildrecords.py 2009-07-17 00:26:05 +0000
846+++ lib/lp/soyuz/interfaces/buildrecords.py 2010-06-16 14:06:25 +0000
847@@ -5,7 +5,7 @@
848
849 """IHasBuildRecords interface.
850
851-Implemented by any object that can have `IBuild` records related to it.
852+Implemented by any object that can have `IPackageBuild` records related to it.
853 """
854
855 __metaclass__ = type
856@@ -45,7 +45,7 @@
857 @operation_returns_collection_of(Interface)
858 @export_read_operation()
859 def getBuildRecords(build_state=None, name=None, pocket=None,
860- arch_tag=None, user=None):
861+ arch_tag=None, user=None, binary_only=True):
862 """Return build records in the context it is implemented.
863
864 It excludes build records generated by Gina (imported from a external
865@@ -66,9 +66,14 @@
866 :param user: optional `IPerson` corresponding to the user performing
867 the request. It will filter out build records for which the user
868 have no 'view' permission.
869+ :param binary_only: optional boolean indicating whether only
870+ `BinaryPackageBuild` objects should be returned, or more general
871+ `PackageBuild` objects (which may include, for example,
872+ `SourcePackageRecipeBuild` objects.
873
874- :return: a result set containing `IBuild` records ordered by descending
875- `IBuild.datebuilt` except when builds are filtered by
876- `BuildStatus.NEEDSBUILD`, in this case records are ordered by
877- descending `BuildQueue.lastscore` (dispatching order).
878+ :return: a result set containing `IPackageBuild` records ordered by
879+ descending `IPackageBuild.date_finished` except when builds are
880+ filtered by `BuildStatus.NEEDSBUILD`, in this case records
881+ are ordered by descending `BuildQueue.lastscore`
882+ (dispatching order).
883 """
884
885=== modified file 'lib/lp/soyuz/model/archive.py'
886--- lib/lp/soyuz/model/archive.py 2010-06-14 12:19:10 +0000
887+++ lib/lp/soyuz/model/archive.py 2010-06-16 14:06:25 +0000
888@@ -34,6 +34,7 @@
889 cursor, quote, quote_like, sqlvalues, SQLBase)
890 from canonical.launchpad.interfaces.lpstorm import ISlaveStore
891 from lp.buildmaster.interfaces.buildbase import BuildStatus
892+from lp.buildmaster.interfaces.packagebuild import IPackageBuildSet
893 from lp.buildmaster.model.buildfarmjob import BuildFarmJob
894 from lp.buildmaster.model.packagebuild import PackageBuild
895 from lp.services.job.interfaces.job import JobStatus
896@@ -65,7 +66,7 @@
897 ArchiveNotPrivate, ArchivePurpose, ArchiveStatus, CannotCopy,
898 CannotSwitchPrivacy, CannotUploadToPPA, CannotUploadToPocket,
899 DistroSeriesNotFound, IArchive, IArchiveSet, IDistributionArchive,
900- InsufficientUploadRights, InvalidPocketForPPA,
901+ IncompatibleArguments, InsufficientUploadRights, InvalidPocketForPPA,
902 InvalidPocketForPartnerArchive, InvalidComponent, IPPA,
903 MAIN_ARCHIVE_PURPOSES, NoRightsForArchive, NoRightsForComponent,
904 NoSuchPPA, NoTokensForTeams, PocketNotFound, VersionRequiresName,
905@@ -369,12 +370,21 @@
906 return None
907
908 def getBuildRecords(self, build_state=None, name=None, pocket=None,
909- arch_tag=None, user=None):
910+ arch_tag=None, user=None, binary_only=True):
911 """See IHasBuildRecords"""
912 # Ignore "user", since anyone already accessing this archive
913 # will implicitly have permission to see it.
914- return getUtility(IBinaryPackageBuildSet).getBuildsForArchive(
915- self, build_state, name, pocket, arch_tag)
916+
917+ if binary_only:
918+ return getUtility(IBinaryPackageBuildSet).getBuildsForArchive(
919+ self, build_state, name, pocket, arch_tag)
920+ else:
921+ if arch_tag is not None or name is not None:
922+ raise IncompatibleArguments(
923+ "The 'arch_tag' and 'name' parameters can be used only "
924+ "with binary_only=True.")
925+ return getUtility(IPackageBuildSet).getBuildsForArchive(
926+ self, status=build_state, pocket=pocket)
927
928 def getPublishedSources(self, name=None, version=None, status=None,
929 distroseries=None, pocket=None,
930
931=== modified file 'lib/lp/soyuz/model/binarypackagebuild.py'
932--- lib/lp/soyuz/model/binarypackagebuild.py 2010-06-09 12:13:53 +0000
933+++ lib/lp/soyuz/model/binarypackagebuild.py 2010-06-16 14:06:25 +0000
934@@ -65,6 +65,27 @@
935 PackageUpload, PackageUploadBuild)
936
937
938+def get_binary_build_for_build_farm_job(build_farm_job):
939+ """Factory method to returning a binary for a build farm job."""
940+ # No need to query the db if the build_farm_job doesn't have
941+ # the correct job type.
942+ if build_farm_job.job_type != BuildFarmJobType.PACKAGEBUILD:
943+ return None
944+
945+ store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
946+ find_spec = (BinaryPackageBuild, PackageBuild, BuildFarmJob)
947+ resulting_tuple = store.find(
948+ find_spec,
949+ BinaryPackageBuild.package_build == PackageBuild.id,
950+ PackageBuild.build_farm_job == BuildFarmJob.id,
951+ BuildFarmJob.id == build_farm_job.id).one()
952+
953+ if resulting_tuple is None:
954+ return None
955+
956+ return resulting_tuple[0]
957+
958+
959 class BinaryPackageBuild(PackageBuildDerived, SQLBase):
960 implements(IBinaryPackageBuild)
961 _table = 'BinaryPackageBuild'
962
963=== modified file 'lib/lp/soyuz/tests/test_archive.py'
964--- lib/lp/soyuz/tests/test_archive.py 2010-06-14 15:58:46 +0000
965+++ lib/lp/soyuz/tests/test_archive.py 2010-06-16 14:06:25 +0000
966@@ -240,7 +240,7 @@
967 ubuntu_test = breezy_autotest.distribution
968 self.series = [breezy_autotest]
969 self.series.append(self.factory.makeDistroRelease(
970- distribution=ubuntu_test, name="foo-series"))
971+ distribution=ubuntu_test, name="foo-series", version='1.0'))
972
973 self.sources = []
974 gedit_src_hist = self.publisher.getPubSource(
975
976=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py'
977--- lib/lp/soyuz/tests/test_binarypackagebuild.py 2010-06-09 12:07:58 +0000
978+++ lib/lp/soyuz/tests/test_binarypackagebuild.py 2010-06-16 14:06:25 +0000
979@@ -15,6 +15,7 @@
980 from canonical.testing import LaunchpadZopelessLayer
981 from lp.services.job.model.job import Job
982 from lp.buildmaster.interfaces.buildbase import BuildStatus
983+from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType
984 from lp.buildmaster.interfaces.builder import IBuilderSet
985 from lp.buildmaster.interfaces.buildqueue import IBuildQueue
986 from lp.buildmaster.interfaces.packagebuild import IPackageBuild
987@@ -120,6 +121,42 @@
988 self.build.build_farm_job.id),
989 self.build.log_url)
990
991+ def test_adapt_from_build_farm_job(self):
992+ # An `IBuildFarmJob` can be adapted to an IBinaryPackageBuild
993+ # if it has the correct job type.
994+ build_farm_job = self.build.build_farm_job
995+ store = Store.of(build_farm_job)
996+ store.flush()
997+
998+ binary_package_build = IBinaryPackageBuild(build_farm_job)
999+ self.failUnlessEqual(self.build, binary_package_build)
1000+
1001+ def test_adapt_from_build_farm_job_wrong_type(self):
1002+ # An `IBuildFarmJob` of the wrong type results is None.
1003+ build_farm_job = self.build.build_farm_job
1004+ removeSecurityProxy(build_farm_job).job_type = (
1005+ BuildFarmJobType.RECIPEBRANCHBUILD)
1006+
1007+ binary_package_build = IBinaryPackageBuild(build_farm_job)
1008+ self.failUnlessEqual(None, binary_package_build)
1009+
1010+ def test_adapt_from_build_farm_job_prefetching(self):
1011+ # The package_build is prefetched for efficiency.
1012+ build_farm_job = self.build.build_farm_job
1013+
1014+ # We clear the cache to avoid getting cached objects where
1015+ # they would normally be queries.
1016+ store = Store.of(build_farm_job)
1017+ store.flush()
1018+ store.reset()
1019+
1020+ binary_package_build = IBinaryPackageBuild(build_farm_job)
1021+
1022+ self.assertStatementCount(
1023+ 0, getattr, binary_package_build, "package_build")
1024+ self.assertStatementCount(
1025+ 0, getattr, binary_package_build, "build_farm_job")
1026+
1027
1028 class TestBuildUpdateDependencies(TestCaseWithFactory):
1029
1030
1031=== modified file 'lib/lp/soyuz/tests/test_hasbuildrecords.py'
1032--- lib/lp/soyuz/tests/test_hasbuildrecords.py 2010-05-06 10:35:17 +0000
1033+++ lib/lp/soyuz/tests/test_hasbuildrecords.py 2010-06-16 14:06:25 +0000
1034@@ -12,6 +12,11 @@
1035
1036 from lp.registry.model.sourcepackage import SourcePackage
1037 from lp.buildmaster.interfaces.builder import IBuilderSet
1038+from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType
1039+from lp.buildmaster.interfaces.packagebuild import (
1040+ IPackageBuildSource)
1041+from lp.registry.interfaces.pocket import PackagePublishingPocket
1042+from lp.soyuz.interfaces.archive import IncompatibleArguments
1043 from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
1044 from lp.soyuz.model.processor import ProcessorFamilySet
1045 from lp.soyuz.tests.test_binarypackagebuild import (
1046@@ -81,6 +86,32 @@
1047
1048 self.context = self.publisher.distroseries.main_archive
1049
1050+ def test_binary_only_false(self):
1051+ # An archive can optionally return the more general
1052+ # package build objects.
1053+
1054+ # Until we have different IBuildFarmJob types implemented, we
1055+ # can only test this by creating a lone PackageBuild of a
1056+ # different type.
1057+ getUtility(IPackageBuildSource).new(
1058+ job_type=BuildFarmJobType.RECIPEBRANCHBUILD, virtualized=True,
1059+ archive=self.context, pocket=PackagePublishingPocket.RELEASE)
1060+
1061+ builds = self.context.getBuildRecords(binary_only=True)
1062+ self.failUnlessEqual(3, builds.count())
1063+
1064+ builds = self.context.getBuildRecords(binary_only=False)
1065+ self.failUnlessEqual(4, builds.count())
1066+
1067+ def test_incompatible_arguments(self):
1068+ # binary_only=False is incompatible with arch_tag and name.
1069+ self.failUnlessRaises(
1070+ IncompatibleArguments, self.context.getBuildRecords,
1071+ binary_only=False, arch_tag="anything")
1072+ self.failUnlessRaises(
1073+ IncompatibleArguments, self.context.getBuildRecords,
1074+ binary_only=False, name="anything")
1075+
1076
1077 class TestBuilderHasBuildRecords(TestHasBuildRecordsInterface):
1078 """Test the Builder implementation of IHasBuildRecords."""
1079
1080=== modified file 'lib/lp/testing/factory.py'
1081--- lib/lp/testing/factory.py 2010-06-13 05:56:31 +0000
1082+++ lib/lp/testing/factory.py 2010-06-16 14:06:25 +0000
1083@@ -22,8 +22,10 @@
1084 from email.mime.text import MIMEText
1085 from email.mime.multipart import MIMEMultipart
1086 from itertools import count
1087+import os.path
1088+from random import randint
1089 from StringIO import StringIO
1090-import os.path
1091+from threading import local
1092
1093 import pytz
1094
1095@@ -56,7 +58,6 @@
1096 from canonical.launchpad.webapp.dbpolicy import MasterDatabasePolicy
1097 from canonical.launchpad.webapp.interfaces import (
1098 IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
1099-from canonical.uuid import generate_uuid
1100
1101 from lp.blueprints.interfaces.specification import (
1102 ISpecificationSet, SpecificationDefinitionStatus)
1103@@ -213,14 +214,22 @@
1104
1105 def __init__(self):
1106 # Initialise the unique identifier.
1107- self._integer = count(1)
1108+ self._local = local()
1109
1110 def getUniqueEmailAddress(self):
1111 return "%s@example.com" % self.getUniqueString('email')
1112
1113 def getUniqueInteger(self):
1114- """Return an integer unique to this factory instance."""
1115- return self._integer.next()
1116+ """Return an integer unique to this factory instance.
1117+
1118+ For each thread, this will be a series of increasing numbers, but the
1119+ starting point will be unique per thread.
1120+ """
1121+ counter = getattr(self._local, 'integer', None)
1122+ if counter is None:
1123+ counter = count(randint(0, 1000000))
1124+ self._local.integer = counter
1125+ return counter.next()
1126
1127 def getUniqueHexString(self, digits=None):
1128 """Return a unique hexadecimal string.
1129@@ -245,7 +254,7 @@
1130 """
1131 if prefix is None:
1132 prefix = "generic-string"
1133- string = "%s%s" % (prefix, generate_uuid())
1134+ string = "%s%s" % (prefix, self.getUniqueInteger())
1135 return string.replace('_', '-').lower()
1136
1137 def getUniqueUnicode(self):

Subscribers

People subscribed via source and target branches

to status/vote changes: