launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #17852
[Merge] lp:~wgrant/launchpad/xbia into lp:launchpad
William Grant has proposed merging lp:~wgrant/launchpad/xbia into lp:launchpad with lp:~wgrant/launchpad/xbia-refactor as a prerequisite.
Commit message:
Source packages can select their arch-indep build architecture using a new Build-Indep-Architecture field.
"all" in a hint list now forces a nominatedarchindep build only if it is the sole term, rather than if it was the only term we could resolve.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #217427 in Launchpad itself: "Please support arbitrary arch/buildd affinity for arch:all builds"
https://bugs.launchpad.net/launchpad/+bug/217427
Bug #1390409 in Launchpad itself: "BinaryPackageBuildSet.createForSource erroneously creates nominatedarchindep builds when arch-restricted"
https://bugs.launchpad.net/launchpad/+bug/1390409
For more details, see:
https://code.launchpad.net/~wgrant/launchpad/xbia/+merge/249296
Source packages can select their arch-indep build architecture using a new Build-Indep-Architecture field.
"all" in a hint list now forces a nominatedarchindep build only if it is the sole term, rather than if it was the only term we could resolve.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/xbia into lp:launchpad.
=== modified file 'lib/lp/soyuz/adapters/buildarch.py'
--- lib/lp/soyuz/adapters/buildarch.py 2015-02-11 10:43:58 +0000
+++ lib/lp/soyuz/adapters/buildarch.py 2015-02-11 10:43:59 +0000
@@ -36,29 +36,62 @@
dpkg_architecture = DpkgArchitectureCache()
-def determine_architectures_to_build(hintlist, valid_archs,
+def resolve_arch_spec(hintlist, valid_archs):
+ hint_archs = set(hintlist.split())
+ # 'all' is only used if it's a purely arch-indep package.
+ if hint_archs == set(["all"]):
+ return set(), True
+ return (
+ set(dpkg_architecture.findAllMatches(valid_archs, hint_archs)), False)
+
+
+def determine_architectures_to_build(hint_list, indep_hint_list, need_archs,
nominated_arch_indep, need_arch_indep):
"""Return a set of architectures to build.
- :param hintlist: A string of the architectures this source package
+ :param hint_list: a string of the architectures this source package
specifies it builds for.
- :param valid_archs: a list of all architecture tags that we can
- create builds for.
- :param nominated_arch_indep: a preferred architecture tag for
- architecture-independent builds. May be None.
- :return: a set of architecture tags for which the source publication in
- question should be built.
+ :param indep_hint_list: a string of the architectures this source package
+ specifies it can build architecture-independent packages on.
+ :param need_archs: an ordered list of all architecture tags that we can
+ create builds for. the first usable one gets the arch-indep flag.
+ :param nominated_arch_indep: the default architecture tag for
+ arch-indep-only packages. may be None.
+ :param need_arch_indep: should an arch-indep build be created if possible?
+ :return: a map of architecture tag to arch-indep flag for each build
+ that should be created.
"""
- hint_archs = set(hintlist.split())
- build_archs = set(
- dpkg_architecture.findAllMatches(valid_archs, hint_archs))
-
- # 'all' is only used as a last resort, to create an arch-indep build
- # where no builds would otherwise exist.
- if need_arch_indep and len(build_archs) == 0 and 'all' in hint_archs:
- if nominated_arch_indep in valid_archs:
- return set([nominated_arch_indep])
- else:
- return set()
-
- return build_archs
+ build_archs, indep_only = resolve_arch_spec(hint_list, need_archs)
+
+ # Use the indep hint list if it's set, otherwise fall back to the
+ # main architecture list. If that's not set either (ie. it's just
+ # "all"), default to nominatedarchindep.
+ if indep_hint_list:
+ indep_archs, _ = resolve_arch_spec(indep_hint_list, need_archs)
+ elif not indep_only:
+ indep_archs = set(build_archs)
+ elif nominated_arch_indep in need_archs:
+ indep_archs = set([nominated_arch_indep])
+ else:
+ indep_archs = set()
+
+ indep_arch = None
+ if need_arch_indep:
+ # Try to avoid adding a new build if an existing one would work.
+ both_archs = set(build_archs) & set(indep_archs)
+ if both_archs:
+ indep_archs = both_archs
+
+ # The ideal arch_indep build is nominatedarchindep. But if we're
+ # not creating a build for it, use the first candidate DAS that
+ # made it this far.
+ for arch in [nominated_arch_indep] + need_archs:
+ if arch in indep_archs:
+ indep_arch = arch
+ break
+
+ # Ensure that we build the indep arch.
+ if indep_arch is not None and indep_arch not in build_archs:
+ build_archs.add(indep_arch)
+
+ return {arch: arch == indep_arch for arch in build_archs}
=== modified file 'lib/lp/soyuz/adapters/tests/test_buildarch.py'
--- lib/lp/soyuz/adapters/tests/test_buildarch.py 2015-02-11 10:43:58 +0000
+++ lib/lp/soyuz/adapters/tests/test_buildarch.py 2015-02-11 10:43:59 +0000
@@ -43,82 +43,140 @@
"""
def assertArchsForHint(self, hint_string, expected_arch_tags,
- allowed_arch_tags=None):
+ allowed_arch_tags=None, indep_hint_list=None,
+ need_arch_indep=True):
if allowed_arch_tags is None:
allowed_arch_tags = ['armel', 'hppa', 'i386']
arch_tags = determine_architectures_to_build(
- hint_string, allowed_arch_tags, 'i386', True)
- self.assertContentEqual(expected_arch_tags, arch_tags)
+ hint_string, indep_hint_list, allowed_arch_tags, 'i386',
+ need_arch_indep)
+ self.assertContentEqual(expected_arch_tags.items(), arch_tags.items())
def test_single_architecture(self):
# A hint string with a single arch resolves to just that arch.
- self.assertArchsForHint('hppa', ['hppa'])
+ self.assertArchsForHint('hppa', {'hppa': True})
def test_three_architectures(self):
# A hint string with multiple archs resolves to just those
# archs.
- self.assertArchsForHint('amd64 i386 hppa', ['hppa', 'i386'])
+ self.assertArchsForHint(
+ 'amd64 i386 hppa', {'hppa': False, 'i386': True})
def test_independent(self):
# 'all' is special, meaning just a single build. The
# nominatedarchindep architecture is used -- in this case i386.
- self.assertArchsForHint('all', ['i386'])
+ self.assertArchsForHint('all', {'i386': True})
def test_one_and_independent(self):
# 'all' is redundant if we have another build anyway.
- self.assertArchsForHint('hppa all', ['hppa'])
+ self.assertArchsForHint('hppa all', {'hppa': True})
def test_fictional_and_independent(self):
- # But 'all' is useful if present with an arch that wouldn't
- # generate a build.
- self.assertArchsForHint('foo all', ['i386'])
+ # 'all' doesn't make an unbuildable string buildable.
+ self.assertArchsForHint('fiction all', {})
def test_wildcard(self):
# 'any' is a wildcard that matches all available archs.
- self.assertArchsForHint('any', ['armel', 'hppa', 'i386'])
+ self.assertArchsForHint(
+ 'any', {'armel': False, 'hppa': False, 'i386': True})
def test_kernel_specific_architecture(self):
# Since we only support Linux-based architectures, 'linux-foo'
# is treated the same as 'foo'.
- self.assertArchsForHint('linux-hppa', ['hppa'])
+ self.assertArchsForHint('linux-hppa', {'hppa': True})
def test_unknown_kernel_specific_architecture(self):
# Non-Linux architectures aren't supported.
- self.assertArchsForHint('kfreebsd-hppa', [])
+ self.assertArchsForHint('kfreebsd-hppa', {})
def test_kernel_wildcard_architecture(self):
# Wildcards work for kernels: 'any-foo' is treated like 'foo'.
- self.assertArchsForHint('any-hppa', ['hppa'])
+ self.assertArchsForHint('any-hppa', {'hppa': True})
def test_kernel_wildcard_architecture_arm(self):
# The second part of a wildcard matches the canonical CPU name, not
# on the Debian architecture, so 'any-arm' matches 'armel'.
- self.assertArchsForHint('any-arm', ['armel'])
+ self.assertArchsForHint('any-arm', {'armel': True})
def test_kernel_specific_architecture_wildcard(self):
# Wildcards work for archs too: 'linux-any' is treated like 'any'.
- self.assertArchsForHint('linux-any', ['armel', 'hppa', 'i386'])
+ self.assertArchsForHint(
+ 'linux-any', {'armel': False, 'hppa': False, 'i386': True})
def test_unknown_kernel_specific_architecture_wildcard(self):
# But unknown kernels continue to result in nothing.
- self.assertArchsForHint('kfreebsd-any', [])
+ self.assertArchsForHint('kfreebsd-any', {})
def test_wildcard_and_independent(self):
# 'all' continues to be ignored alongside a valid wildcard.
- self.assertArchsForHint('all linux-any', ['armel', 'hppa', 'i386'])
+ self.assertArchsForHint(
+ 'all linux-any', {'armel': False, 'hppa': False, 'i386': True})
def test_kernel_independent_is_invalid(self):
# 'linux-all' isn't supported.
- self.assertArchsForHint('linux-all', [])
+ self.assertArchsForHint('linux-all', {})
def test_double_wildcard_is_same_as_single(self):
# 'any-any' is redundant with 'any', but dpkg-architecture supports
# it anyway.
- self.assertArchsForHint('any-any', ['armel', 'hppa', 'i386'])
+ self.assertArchsForHint(
+ 'any-any', {'armel': False, 'hppa': False, 'i386': True})
def test_no_all_builds_when_nominatedarchindep_not_permitted(self):
# Some archives (eg. armel rebuilds) don't want arch-indep
# builds. If the nominatedarchindep architecture (normally
# i386) is omitted, no builds will be created for arch-indep
# sources.
- self.assertArchsForHint('all', [], allowed_arch_tags=['hppa'])
+ self.assertArchsForHint('all', {}, allowed_arch_tags=['hppa'])
+
+ def test_indep_hint_only(self):
+ # Some packages need to build arch-indep builds on a specific
+ # architecture, declared using XS-Build-Indep-Architecture.
+ self.assertArchsForHint('all', {'hppa': True}, indep_hint_list='hppa')
+
+ def test_indep_hint_only_multiple(self):
+ # The earliest available architecture in the available list (not
+ # the hint list) is chosen.
+ self.assertArchsForHint(
+ 'all', {'armel': True}, indep_hint_list='armel hppa')
+
+ def test_indep_hint_only_unsatisfiable(self):
+ # An indep hint list that matches nothing results in no builds
+ self.assertArchsForHint('all', {}, indep_hint_list='fiction')
+
+ def test_indep_hint(self):
+ # Unlike nominatedarchindep, a hinted indep will cause an
+ # additional build to be created if necessary.
+ self.assertArchsForHint(
+ 'armel all', {'armel': False, 'hppa': True},
+ indep_hint_list='hppa')
+
+ def test_indep_hint_wildcard(self):
+ # An indep hint list can include wildcards.
+ self.assertArchsForHint(
+ 'armel all', {'armel': False, 'hppa': True},
+ indep_hint_list='any-hppa')
+
+ def test_indep_hint_coalesces(self):
+ # An indep hint list that matches an existing build will avoid
+ # creating another.
+ self.assertArchsForHint(
+ 'hppa all', {'hppa': True}, indep_hint_list='linux-any')
+
+ def test_indep_hint_unsatisfiable(self):
+ # An indep hint list that matches nothing results in no
+ # additional builds
+ self.assertArchsForHint(
+ 'armel all', {'armel': False}, indep_hint_list='fiction')
+
+ def test_no_need_arch_indep(self):
+ self.assertArchsForHint(
+ 'armel all', {'armel': False}, need_arch_indep=False)
+
+ def test_no_need_arch_indep_hint(self):
+ self.assertArchsForHint(
+ 'armel all', {'armel': False}, indep_hint_list='hppa',
+ need_arch_indep=False)
+
+ def test_no_need_arch_indep_only(self):
+ self.assertArchsForHint('all', {}, need_arch_indep=False)
=== modified file 'lib/lp/soyuz/interfaces/sourcepackagerelease.py'
--- lib/lp/soyuz/interfaces/sourcepackagerelease.py 2014-11-09 23:28:27 +0000
+++ lib/lp/soyuz/interfaces/sourcepackagerelease.py 2015-02-11 10:43:59 +0000
@@ -151,6 +151,9 @@
title=_("Source package recipe build"),
required=False, readonly=True)
+ def getUserDefinedField(name):
+ """Case-insensitively get a user-defined field."""
+
def addFile(file, filetype=None):
"""Add the provided library file alias (file) to the list of files
in this package.
=== modified file 'lib/lp/soyuz/model/binarypackagebuild.py'
--- lib/lp/soyuz/model/binarypackagebuild.py 2015-02-11 10:43:58 +0000
+++ lib/lp/soyuz/model/binarypackagebuild.py 2015-02-11 10:43:59 +0000
@@ -1423,56 +1423,44 @@
# this series already has it set.
need_arch_indep = not any(bpb.arch_indep for bpb in relevant_builds)
- # Find the architectures for which the source should end up with
- # binaries, parsing architecturehintlist as you'd expect.
- # For an architecturehintlist of just 'all', this will
- # be the current nominatedarchindep if need_arch_indep,
- # otherwise nothing.
- valid_archs = list(
- architectures_available or distroseries.buildable_architectures)
- valid_archs = self._getAllowedArchitectures(archive, valid_archs)
+ # Find the architectures for which the source chould end up with
+ # new binaries. Exclude architectures not allowed in this
+ # archive and architectures that have already built. Order by
+ # Processor.id so determine_architectures_to_build is
+ # deterministic.
+ # XXX wgrant 2014-11-06: The fact that production's
+ # Processor 1 is i386, a good arch-indep candidate, is a
+ # total coincidence and this isn't a hack. I promise.
+ need_archs = sorted(
+ [das for das in
+ self._getAllowedArchitectures(
+ archive,
+ architectures_available
+ or distroseries.buildable_architectures)
+ if das.architecturetag not in skip_archtags],
+ key=attrgetter('processor.id'))
nominated_arch_indep_tag = (
distroseries.nominatedarchindep.architecturetag
if distroseries.nominatedarchindep else None)
- # Filter the valid archs against the hint list.
- candidate_arch_tags = determine_architectures_to_build(
+ # Filter the valid archs against the hint list and work out
+ # their arch-indepness.
+ create_tag_map = determine_architectures_to_build(
sourcepackagerelease.architecturehintlist,
- [das.architecturetag for das in valid_archs],
+ sourcepackagerelease.getUserDefinedField(
+ 'Build-Indep-Architecture'),
+ [das.architecturetag for das in need_archs],
nominated_arch_indep_tag, need_arch_indep)
- candidate_architectures = set(
- das for das in valid_archs
- if das.architecturetag in candidate_arch_tags)
-
- # Filter out any architectures for which we earlier found sufficient
- # builds.
- needed_architectures = [
- das for das in candidate_architectures
- if das.architecturetag not in skip_archtags]
- if not needed_architectures:
- return []
-
- arch_indep_das = None
- if need_arch_indep:
- # The ideal arch_indep build is nominatedarchindep. But if
- # that isn't a build we would otherwise create, use the DAS
- # with the lowest Processor.id.
- # XXX wgrant 2014-11-06: The fact that production's
- # Processor 1 is i386, a good arch-indep candidate, is a
- # total coincidence and this isn't a hack. I promise.
- if distroseries.nominatedarchindep in needed_architectures:
- arch_indep_das = distroseries.nominatedarchindep
- else:
- arch_indep_das = sorted(
- needed_architectures, key=attrgetter('processor.id'))[0]
# Create builds for the remaining architectures.
new_builds = []
- for das in needed_architectures:
+ for das in need_archs:
+ if das.architecturetag not in create_tag_map:
+ continue
build = self.new(
source_package_release=sourcepackagerelease,
distro_arch_series=das, archive=archive, pocket=pocket,
- arch_indep=das == arch_indep_das)
+ arch_indep=create_tag_map[das.architecturetag])
new_builds.append(build)
# Create the builds in suspended mode for disabled archives.
build_queue = build.queueBuild(suspended=not archive.enabled)
=== modified file 'lib/lp/soyuz/model/sourcepackagerelease.py'
--- lib/lp/soyuz/model/sourcepackagerelease.py 2014-11-09 23:28:27 +0000
+++ lib/lp/soyuz/model/sourcepackagerelease.py 2015-02-11 10:43:59 +0000
@@ -170,6 +170,12 @@
return []
return simplejson.loads(self._user_defined_fields)
+ def getUserDefinedField(self, name):
+ if self.user_defined_fields:
+ for k, v in self.user_defined_fields.iteritems():
+ if k.lower() == name.lower():
+ return v
+
@cachedproperty
def package_diffs(self):
return list(Store.of(self).find(
=== modified file 'lib/lp/soyuz/tests/test_build_set.py'
--- lib/lp/soyuz/tests/test_build_set.py 2015-02-11 10:43:58 +0000
+++ lib/lp/soyuz/tests/test_build_set.py 2015-02-11 10:43:59 +0000
@@ -491,6 +491,19 @@
new_builds = self.createBuilds(spr, self.distroseries)
self.assertBuildsMatch({}, new_builds)
+ def test_createForSource_build_indep_architecture(self):
+ # A user defined field of Build-Indep-Architecture provides a
+ # custom hint list to override Architecture and
+ # nominatedarchindep for arch-indep purposes.
+ spr = self.factory.makeSourcePackageRelease(
+ architecturehintlist='sparc all',
+ user_defined_fields={'build-indep-architecture': 'avr'})
+ builds = self.createBuilds(spr, self.distroseries2)
+ self.assertBuildsMatch({'sparc': False, 'avr': True}, builds)
+ self.completeBuilds(builds, {'sparc': True, 'avr': True})
+ new_builds = self.createBuilds(spr, self.distroseries)
+ self.assertBuildsMatch({}, new_builds)
+
class TestFindBuiltOrPublishedBySourceAndArchive(TestCaseWithFactory):
"""Tests for findBuiltOrPublishedBySourceAndArchive()."""
Follow ups