Merge lp:~wgrant/launchpad/bug-1350208 into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 17231
Proposed branch: lp:~wgrant/launchpad/bug-1350208
Merge into: lp:launchpad
Diff against target: 308 lines (+181/-50)
4 files modified
lib/lp/soyuz/adapters/buildarch.py (+2/-2)
lib/lp/soyuz/adapters/tests/test_buildarch.py (+1/-1)
lib/lp/soyuz/model/binarypackagebuild.py (+75/-47)
lib/lp/soyuz/tests/test_build_set.py (+103/-0)
To merge this branch: bzr merge lp:~wgrant/launchpad/bug-1350208
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+240808@code.launchpad.net

Commit message

Fix createForSource to correctly handle unusual arch-indep cases (eg. changes in nominatedarchindep, or "Architecture: powerpc all").

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/soyuz/adapters/buildarch.py'
2--- lib/lp/soyuz/adapters/buildarch.py 2013-05-31 04:27:26 +0000
3+++ lib/lp/soyuz/adapters/buildarch.py 2014-11-06 05:30:00 +0000
4@@ -37,7 +37,7 @@
5
6
7 def determine_architectures_to_build(hintlist, archive, distroseries,
8- legal_archseries):
9+ legal_archseries, need_arch_indep):
10 """Return a list of architectures for which this publication should build.
11
12 This function answers the question: given a list of architectures and
13@@ -78,7 +78,7 @@
14
15 # 'all' is only used as a last resort, to create an arch-indep build
16 # where no builds would otherwise exist.
17- if len(build_tags) == 0 and 'all' in hint_archs:
18+ if need_arch_indep and len(build_tags) == 0 and 'all' in hint_archs:
19 nominated_arch = distroseries.nominatedarchindep
20 if nominated_arch in legal_archseries:
21 build_tags = set([nominated_arch.architecturetag])
22
23=== modified file 'lib/lp/soyuz/adapters/tests/test_buildarch.py'
24--- lib/lp/soyuz/adapters/tests/test_buildarch.py 2013-09-11 06:05:44 +0000
25+++ lib/lp/soyuz/adapters/tests/test_buildarch.py 2014-11-06 05:30:00 +0000
26@@ -34,7 +34,7 @@
27 if arch.architecturetag in allowed_arch_tags]
28 architectures = determine_architectures_to_build(
29 pub.sourcepackagerelease.architecturehintlist, pub.archive,
30- self.publisher.breezy_autotest, allowed_archs)
31+ self.publisher.breezy_autotest, allowed_archs, True)
32 self.assertContentEqual(
33 expected_arch_tags, [a.architecturetag for a in architectures])
34
35
36=== modified file 'lib/lp/soyuz/model/binarypackagebuild.py'
37--- lib/lp/soyuz/model/binarypackagebuild.py 2014-11-06 00:33:31 +0000
38+++ lib/lp/soyuz/model/binarypackagebuild.py 2014-11-06 05:30:00 +0000
39@@ -13,7 +13,10 @@
40 ]
41
42 import datetime
43-from operator import itemgetter
44+from operator import (
45+ attrgetter,
46+ itemgetter,
47+ )
48
49 import apt_pkg
50 import pytz
51@@ -1383,58 +1386,83 @@
52 if not distroarch.processor.restricted or
53 distroarch.processor in archive.enabled_restricted_processors]
54
55- def _createMissingBuildForArchitecture(self, sourcepackagerelease,
56- archive, arch, pocket,
57- logger=None):
58- """Create a build for a given architecture if it doesn't exist yet.
59-
60- Return the just-created `IBinaryPackageBuild` record already
61- scored or None if a suitable build is already present.
62- """
63- exact_build = self.getBySourceAndLocation(
64- sourcepackagerelease, archive, arch)
65- if exact_build is not None:
66- return None
67-
68- build_candidate = self.findBuiltOrPublishedBySourceAndArchive(
69- sourcepackagerelease, archive).get(arch.architecturetag)
70- if build_candidate is not None:
71- return None
72-
73- build = self.new(
74- source_package_release=sourcepackagerelease,
75- distro_arch_series=arch, archive=archive, pocket=pocket)
76- # Create the builds in suspended mode for disabled archives.
77- build_queue = build.queueBuild(suspended=not archive.enabled)
78- Store.of(build).flush()
79-
80- if logger is not None:
81- logger.debug(
82- "Created %s [%d] in %s (%d)"
83- % (build.title, build.id, build.archive.displayname,
84- build_queue.lastscore))
85-
86- return build
87-
88 def createForSource(self, sourcepackagerelease, archive, distroseries,
89 pocket, architectures_available=None, logger=None):
90 """See `ISourcePackagePublishingHistory`."""
91+
92+ # Exclude any architectures which already have built or copied
93+ # binaries. A new build with the same archtag could never
94+ # succeed; its files would conflict during upload.
95+ relevant_builds = self.findBuiltOrPublishedBySourceAndArchive(
96+ sourcepackagerelease, archive).values()
97+
98+ # Find any architectures that already have a build that exactly
99+ # matches, regardless of status. We can't create a second build
100+ # with the same (SPR, Archive, DAS).
101+ # XXX wgrant 2014-11-06: Should use a bulk query.
102+ for das in distroseries.architectures:
103+ build = self.getBySourceAndLocation(
104+ sourcepackagerelease, archive, das)
105+ if build is not None:
106+ relevant_builds.append(build)
107+
108+ skip_archtags = set(
109+ bpb.distro_arch_series.architecturetag for bpb in relevant_builds)
110+ # We need to assign the arch-indep role to a build unless an
111+ # arch-indep build has already succeeded, or another build in
112+ # this series already has it set.
113+ need_arch_indep = not any(bpb.arch_indep for bpb in relevant_builds)
114+
115+ # Find the architectures for which the source should end up with
116+ # binaries, parsing architecturehintlist as you'd expect.
117+ # For an architecturehintlist of just 'all', this will
118+ # be the current nominatedarchindep if need_arch_indep,
119+ # otherwise nothing.
120 if architectures_available is None:
121 architectures_available = list(
122 distroseries.buildable_architectures)
123-
124 architectures_available = self._getAllowedArchitectures(
125 archive, architectures_available)
126-
127- build_architectures = determine_architectures_to_build(
128+ candidate_architectures = determine_architectures_to_build(
129 sourcepackagerelease.architecturehintlist, archive, distroseries,
130- architectures_available)
131-
132- builds = []
133- for arch in build_architectures:
134- build_candidate = self._createMissingBuildForArchitecture(
135- sourcepackagerelease, archive, arch, pocket, logger=logger)
136- if build_candidate is not None:
137- builds.append(build_candidate)
138-
139- return builds
140+ architectures_available, need_arch_indep)
141+
142+ # Filter out any architectures for which we earlier found sufficient
143+ # builds.
144+ needed_architectures = [
145+ das for das in candidate_architectures
146+ if das.architecturetag not in skip_archtags]
147+ if not needed_architectures:
148+ return []
149+
150+ arch_indep_das = None
151+ if need_arch_indep:
152+ # The ideal arch_indep build is nominatedarchindep. But if
153+ # that isn't a build we would otherwise create, use the DAS
154+ # with the lowest Processor.id.
155+ # XXX wgrant 2014-11-06: The fact that production's
156+ # Processor 1 is i386, a good arch-indep candidate, is a
157+ # total coincidence and this isn't a hack. I promise.
158+ if distroseries.nominatedarchindep in needed_architectures:
159+ arch_indep_das = distroseries.nominatedarchindep
160+ else:
161+ arch_indep_das = sorted(
162+ needed_architectures, key=attrgetter('processor.id'))[0]
163+
164+ # Create builds for the remaining architectures.
165+ new_builds = []
166+ for das in needed_architectures:
167+ build = self.new(
168+ source_package_release=sourcepackagerelease,
169+ distro_arch_series=das, archive=archive, pocket=pocket,
170+ arch_indep=das == arch_indep_das)
171+ new_builds.append(build)
172+ # Create the builds in suspended mode for disabled archives.
173+ build_queue = build.queueBuild(suspended=not archive.enabled)
174+ Store.of(build).flush()
175+ if logger is not None:
176+ logger.debug(
177+ "Created %s [%d] in %s (%d)"
178+ % (build.title, build.id, build.archive.displayname,
179+ build_queue.lastscore))
180+ return new_builds
181
182=== modified file 'lib/lp/soyuz/tests/test_build_set.py'
183--- lib/lp/soyuz/tests/test_build_set.py 2014-11-06 03:33:36 +0000
184+++ lib/lp/soyuz/tests/test_build_set.py 2014-11-06 05:30:00 +0000
185@@ -256,6 +256,16 @@
186 self.distroseries.nominatedarchindep = self.distroseries['sparc']
187 self.addFakeChroots(self.distroseries)
188
189+ self.distroseries2 = self.factory.makeDistroSeries(
190+ distribution=self.distro, name="dumb")
191+ for name, arch in (('avr', self.avr), ('sparc', self.sparc),
192+ ('x32', self.x32)):
193+ self.factory.makeDistroArchSeries(
194+ architecturetag=name, processor=arch,
195+ distroseries=self.distroseries2, supports_virtualized=True)
196+ self.distroseries2.nominatedarchindep = self.distroseries2['x32']
197+ self.addFakeChroots(self.distroseries2)
198+
199 def getPubSource(self, architecturehintlist):
200 """Return a mock source package publishing record for the archive
201 and architecture used in this testcase.
202@@ -281,6 +291,17 @@
203 self.assertContentEqual(expected.items(), actual.items())
204 self.assertEqual(len(actual), len(builds))
205
206+ def completeBuilds(self, builds, success_map):
207+ for build in builds:
208+ success_or_failure = success_map.get(
209+ build.distro_arch_series.architecturetag, None)
210+ if success_or_failure is not None:
211+ build.updateStatus(
212+ BuildStatus.FULLYBUILT if success_or_failure
213+ else BuildStatus.FAILEDTOBUILD)
214+ del success_map[build.distro_arch_series.architecturetag]
215+ self.assertContentEqual([], success_map)
216+
217 def test__getAllowedArchitectures_restricted(self):
218 """Test _getAllowedArchitectures doesn't return unrestricted
219 archs.
220@@ -353,6 +374,88 @@
221 builds = self.createBuilds(spr, self.distroseries)
222 self.assertBuildsMatch({'sparc': True, 'avr': False}, builds)
223
224+ def test_createForSource_arch_indep_from_scratch(self):
225+ """createForSource() sets arch_indep=True on builds for the
226+ nominatedarchindep architecture when no builds already exist.
227+ """
228+ spr = self.factory.makeSourcePackageRelease(architecturehintlist='any')
229+ builds = self.createBuilds(spr, self.distroseries)
230+ self.assertBuildsMatch({'sparc': True, 'avr': False}, builds)
231+
232+ def test_createForSource_any_with_nai_change(self):
233+ # A new non-arch-indep build is created for a new
234+ # nominatedarchindep architecture if arch-indep has already
235+ # built elsewhere.
236+ #
237+ # This is most important when copying with binaries between
238+ # series with different nominatedarchdep (bug #1350208).
239+ spr = self.factory.makeSourcePackageRelease(architecturehintlist='any')
240+ builds = self.createBuilds(spr, self.distroseries)
241+ self.assertBuildsMatch({'sparc': True, 'avr': False}, builds)
242+ self.completeBuilds(builds, {'sparc': True, 'avr': True})
243+ # The new nominatedarchindep needs to be built, but we already
244+ # have arch-indep binaries so arch_indep is False.
245+ new_builds = self.createBuilds(spr, self.distroseries2)
246+ self.assertBuildsMatch({'x32': False}, new_builds)
247+
248+ def test_createForSource_any_with_nai_change_and_fail(self):
249+ # When the previous arch-indep build has failed, and
250+ # nominatedarchindep has changed in the new series, the new
251+ # nominatedarchindep has arch_indep=True while the other arch
252+ # has arch_indep=False.
253+ spr = self.factory.makeSourcePackageRelease(architecturehintlist='any')
254+ builds = self.createBuilds(spr, self.distroseries)
255+ self.assertBuildsMatch({'sparc': True, 'avr': False}, builds)
256+ self.completeBuilds(builds, {'sparc': False, 'avr': True})
257+ # The new nominatedarchindep needs to be built, and the previous
258+ # nominatedarchindep build failed. We end up with two new
259+ # builds, and arch_indep on nominatedarchindep.
260+ new_builds = self.createBuilds(spr, self.distroseries2)
261+ self.assertBuildsMatch({'x32': True, 'sparc': False}, new_builds)
262+
263+ def test_createForSource_all_with_nai_change(self):
264+ # If we only need arch-indep binaries and they've already built
265+ # successfully, no build is created for the new series, even if
266+ # nominatedarchindep has changed.
267+ spr = self.factory.makeSourcePackageRelease(architecturehintlist='all')
268+ builds = self.createBuilds(spr, self.distroseries)
269+ self.assertBuildsMatch({'sparc': True}, builds)
270+ self.completeBuilds(builds, {'sparc': True})
271+ # Despite there being no build for the new nominatedarchindep,
272+ # the old arch-indep build is sufficient and no new record is
273+ # created.
274+ new_builds = self.createBuilds(spr, self.distroseries2)
275+ self.assertBuildsMatch({}, new_builds)
276+
277+ def test_createForSource_all_with_nai_change_and_fail(self):
278+ # If the previous arch-indep sole build failed, a new arch-indep
279+ # build is created for nominatedarchindep.
280+ spr = self.factory.makeSourcePackageRelease(architecturehintlist='all')
281+ builds = self.createBuilds(spr, self.distroseries)
282+ self.assertBuildsMatch({'sparc': True}, builds)
283+ self.completeBuilds(builds, {'sparc': False})
284+ # Despite there being no build for the new nominatedarchindep,
285+ # the old arch-indep build is sufficient and no new record is
286+ # created.
287+ new_builds = self.createBuilds(spr, self.distroseries2)
288+ self.assertBuildsMatch({'x32': True}, new_builds)
289+
290+ def test_createForSource_all_and_other_archs(self):
291+ # If a source package specifies both 'all' and a set of
292+ # architectures that doesn't include nominatedarchindep,
293+ # arch_indep is set on the available DistroArchSeries with the
294+ # oldest Processor.
295+ # This is mostly a hack to avoid hardcoding a preference for
296+ # the faster x86-family architectures, so we don't accidentally
297+ # build documentation on hppa.
298+ spr = self.factory.makeSourcePackageRelease(
299+ architecturehintlist='all sparc avr')
300+ builds = self.createBuilds(spr, self.distroseries2)
301+ self.assertBuildsMatch({'sparc': False, 'avr': True}, builds)
302+ self.completeBuilds(builds, {'sparc': True, 'avr': True})
303+ new_builds = self.createBuilds(spr, self.distroseries)
304+ self.assertBuildsMatch({}, new_builds)
305+
306
307 class TestFindBuiltOrPublishedBySourceAndArchive(TestCaseWithFactory):
308 """Tests for findBuiltOrPublishedBySourceAndArchive()."""