Hi!
I've been contemplating deb822's support for build profiles some more and in
particular looked the recent changes to the build profiles / build restrictions
specification. The attached patches are my current thinking on the matter.
The main changes are:
* there now can be multiple sets of < … > restrictions specified for each
package
* as a result, we need to return a list of restrictions not a single
restriction
* I propose flattening the representation of each restriction from (enabled,
(namespace, label)) to (enabled, namespace, label)
* Following John's suggestion, I'm looking to use namedtuples to provide
backwards compatibility for the architecture restrictions while also providing
a nicer syntax for accessors to both arch and build restrictions objects.
As you will see in the patches, that means that
"texlive <!profile.cross>"
becomes
[[(false, 'profile', 'cross')]]
and
'debhelper (>> 5.0.0) <!profile.stage1> <!profile.cross !profile.stage2>'
becomes
[
[(False, 'profile', 'stage1')],
[(False, 'profile', 'stage2'),
(False, 'profile', 'cross')]
]
The innermost tuple is also accessible as a namedtuple (enabled, namespace,
label).
Comments and suggestions welcome!
cheers
Stuart
--
Stuart Prescott http://www.nanonanonano.net/ [email protected]
Debian Developer http://www.debian.org/ [email protected]
GPG fingerprint 90E2 D2C1 AD14 6A1B 7EBB 891D BBC1 7EBB 1396 F2F7
From 990e668abb13808928df64eb092c9851969adcc2 Mon Sep 17 00:00:00 2001
From: Stuart Prescott <[email protected]>
Date: Sun, 31 Aug 2014 20:14:20 +1000
Subject: [PATCH 1/2] Use namedtuple for architecture restrictions
---
lib/debian/deb822.py | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/lib/debian/deb822.py b/lib/debian/deb822.py
index b40fd38..25a204f 100644
--- a/lib/debian/deb822.py
+++ b/lib/debian/deb822.py
@@ -36,6 +36,7 @@ except (ImportError, AttributeError):
_have_apt_pkg = False
import chardet
+import collections
import os
import re
import subprocess
@@ -898,6 +899,9 @@ class PkgRelation(object):
r'(?P<namespace>[^.]+)\.'
r'(?P<name>[^\s]+)')
+ ArchRestriction = collections.namedtuple('ArchRestriction',
+ ['enabled', 'arch'])
+
@classmethod
def parse_relations(cls, raw):
"""Parse a package relationship string (i.e. the value of a field like
@@ -907,10 +911,10 @@ class PkgRelation(object):
# assumption: no space between '!' and architecture name
archs = []
for arch in cls.__blank_sep_RE.split(raw.strip()):
- if len(arch) and arch[0] == '!':
- archs.append((False, arch[1:]))
- else:
- archs.append((True, arch))
+ disabled = arch[0] == '!'
+ if disabled:
+ arch = arch[1:]
+ archs.append(cls.ArchRestriction(not disabled, arch))
return archs
def parse_profiles(raw):
@@ -972,11 +976,10 @@ class PkgRelation(object):
suitable to be written in a package stanza.
"""
def pp_arch(arch_spec):
- (excl, arch) = arch_spec
- if excl:
- return arch
- else:
- return '!' + arch
+ return '%s%s' % (
+ '' if arch_spec.enabled else '!',
+ arch_spec.arch,
+ )
def pp_atomic_dep(dep):
s = dep['name']
--
1.9.1
From b8c2dbde7989c97d84054a528a8739f85bf61fb1 Mon Sep 17 00:00:00 2001
From: Stuart Prescott <[email protected]>
Date: Sun, 31 Aug 2014 20:22:06 +1000
Subject: [PATCH 2/2] Update build restrictions parser for new syntax
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Support multiple sets of <…> expressions
* Correctly call it "namespace.label" rather than "namespace.name".
* Use namedtuples for BuildRestrictions in symmetry with ArchRestrictions
* Add support for turning build restrictions back into strings
* Update documentation for _PkgRelationMixin to include build restrictions
* Update tests with examples of multiple sets of <…>
---
lib/debian/deb822.py | 71 ++++++++++++++++++++++++++++++++------------
tests/test_Sources | 2 +-
tests/test_Sources.iso8859-1 | 2 +-
tests/test_deb822.py | 46 ++++++++++++++++++++++++----
4 files changed, 95 insertions(+), 26 deletions(-)
diff --git a/lib/debian/deb822.py b/lib/debian/deb822.py
index 25a204f..43413a4 100644
--- a/lib/debian/deb822.py
+++ b/lib/debian/deb822.py
@@ -889,18 +889,21 @@ class PkgRelation(object):
r'(\s*\(\s*(?P<relop>[>=<]+)\s*'
r'(?P<version>[0-9a-zA-Z:\-+~.]+)\s*\))?'
r'(\s*\[(?P<archs>[\s!\w\-]+)\])?\s*'
- r'(<(?P<restrictions>.+)>)?\s*'
+ r'((?P<restrictions><.+>))?\s*'
r'$')
__comma_sep_RE = re.compile(r'\s*,\s*')
__pipe_sep_RE = re.compile(r'\s*\|\s*')
__blank_sep_RE = re.compile(r'\s*')
+ __restriction_sep_RE = re.compile(r'>\s*<')
__restriction_RE = re.compile(
r'(?P<enabled>\!)?'
r'(?P<namespace>[^.]+)\.'
- r'(?P<name>[^\s]+)')
+ r'(?P<label>[^\s]+)')
ArchRestriction = collections.namedtuple('ArchRestriction',
['enabled', 'arch'])
+ BuildRestriction = collections.namedtuple('BuildRestriction',
+ ['enabled', 'namespace', 'label'])
@classmethod
def parse_relations(cls, raw):
@@ -917,27 +920,31 @@ class PkgRelation(object):
archs.append(cls.ArchRestriction(not disabled, arch))
return archs
- def parse_profiles(raw):
+ def parse_restrictions(raw):
""" split a string of restrictions into a list of restrictions
Each restriction is a tuple of form:
- (active, (namespace, name))
+ (active, namespace, label)
where
active: boolean: whether the restriction is positive or negative
namespace: the namespace of the restriction e.g. 'profile'
- name: the name of the restriction e.g. 'stage1'
+ label: the name of the restriction e.g. 'stage1'
"""
restrictions = []
- for restriction in cls.__blank_sep_RE.split(raw.lower().strip()):
- match = cls.__restriction_RE.match(restriction)
- if match:
- parts = match.groupdict()
- restrictions.append((
- parts['enabled'] != '!',
- (parts['namespace'], parts['name']),
- ))
+ for rgrp in cls.__restriction_sep_RE.split(raw.lower().strip('<> ')):
+ group = []
+ for restriction in cls.__blank_sep_RE.split(rgrp):
+ match = cls.__restriction_RE.match(restriction)
+ if match:
+ parts = match.groupdict()
+ group.append(cls.BuildRestriction(
+ parts['enabled'] != '!',
+ parts['namespace'],
+ parts['label'],
+ ))
+ restrictions.append(group)
return restrictions
@@ -957,7 +964,7 @@ class PkgRelation(object):
if parts['archs']:
d['arch'] = parse_archs(parts['archs'])
if parts['restrictions']:
- d['restrictions'] = parse_profiles(parts['restrictions'])
+ d['restrictions'] = parse_restrictions(parts['restrictions'])
return d
else:
warnings.warn('cannot parse package' \
@@ -981,12 +988,25 @@ class PkgRelation(object):
arch_spec.arch,
)
+ def pp_restrictions(restrictions):
+ s = []
+ for r in restrictions:
+ s.append('%s%s.%s' % (
+ '' if r.enabled else '!',
+ r.namespace,
+ r.label
+ )
+ )
+ return '<%s>' % ' '.join(s)
+
def pp_atomic_dep(dep):
s = dep['name']
if dep.get('version') is not None:
s += ' (%s %s)' % dep['version']
if dep.get('arch') is not None:
s += ' [%s]' % ' '.join(map(pp_arch, dep['arch']))
+ if dep.get('restrictions') is not None:
+ s += ' %s' % ' '.join(map(pp_restrictions, dep['restrictions']))
return s
pp_or_dep = lambda deps: ' | '.join(map(pp_atomic_dep, deps))
@@ -1053,7 +1073,7 @@ class _PkgRelationMixin(object):
The encoding of package relationships is as follows:
- the top-level lists corresponds to the comma-separated list of
- Deb822, their components form a conjuction, i.e. they have to be
+ Deb822, their components form a conjunction, i.e. they have to be
AND-ed together
- the inner lists corresponds to the pipe-separated list of Deb822,
their components form a disjunction, i.e. they have to be OR-ed
@@ -1064,11 +1084,20 @@ class _PkgRelationMixin(object):
versioned, None otherwise. operator is one of "<<",
"<=", "=", ">=", ">>"; version is the given version as
a string.
- - arch: A list of pairs <polarity, architecture> if the
+ - arch: A list of pairs <enabled, arch> if the
relationship is architecture specific, None otherwise.
- Polarity is a boolean (false if the architecture is
- negated with "!", true otherwise), architecture the
- Debian archtiecture name as a string.
+ Enabled is a boolean (false if the architecture is
+ negated with "!", true otherwise), arch the
+ Debian architecture name as a string.
+ - restrictions: A list of lists of tuples <enabled, namespace, label>
+ if there are restrictions defined, None otherwise.
+ Enabled is a boolean (false if the restriction is
+ negated with "!", true otherwise), namespace and label
+ are as defined for the build restrictions/profiles.
+
+ The arch and restrictions tuples are available as named tuples so
+ elements are available as some_restriction[0] or alternatively as
+ some_restriction.enabled (and so forth).
Examples:
@@ -1080,6 +1109,10 @@ class _PkgRelationMixin(object):
"tcl8.4-dev, procps [!hurd-i386]" becomes
[ [ {'name': 'tcl8.4-dev'} ],
[ {'name': 'procps', 'arch': (false, 'hurd-i386')} ] ]
+
+ "texlive <!profile.cross>" becomes
+ [ [ {'name': 'texlive',
+ 'restriction': [[(false, 'profile', 'cross')]]} ] ]
"""
if not self.__parsed_relations:
lazy_rels = filter(lambda n: self.__relations[n] is None,
diff --git a/tests/test_Sources b/tests/test_Sources
index 1ffab0d..4357219 100644
--- a/tests/test_Sources
+++ b/tests/test_Sources
@@ -26,7 +26,7 @@ Version: 2.18.1~cvs20080103-6
Priority: optional
Section: devel
Maintainer: James Troup <[email protected]>
-Build-Depends: dpkg-dev (>= 1.13.9), autoconf (>= 2.13), bash, bison:amd64, flex, gettext:any, texinfo <!profile.stage1 !profile.cross>, expect-tcl8.3 (>= 5.32.2) [hppa] <!profile.stage1>, dejagnu (>= 1.4.2-1.1), dpatch, file, bzip2:native, lsb-release
+Build-Depends: dpkg-dev (>= 1.13.9), autoconf (>= 2.13), bash, bison:amd64, flex, gettext:any, texinfo <!profile.stage1> <!profile.stage2 !profile.cross>, expect-tcl8.3 (>= 5.32.2) [hppa] <!profile.stage1>, dejagnu (>= 1.4.2-1.1), dpatch, file, bzip2:native, lsb-release
Architecture: any
Standards-Version: 3.7.3
Format: 1.0
diff --git a/tests/test_Sources.iso8859-1 b/tests/test_Sources.iso8859-1
index 948b190..ff2d999 100644
--- a/tests/test_Sources.iso8859-1
+++ b/tests/test_Sources.iso8859-1
@@ -26,7 +26,7 @@ Version: 2.18.1~cvs20080103-6
Priority: optional
Section: devel
Maintainer: James Troup <[email protected]>
-Build-Depends: dpkg-dev (>= 1.13.9), autoconf (>= 2.13), bash, bison:amd64, flex, gettext:any, texinfo <!profile.stage1 !profile.cross>, expect-tcl8.3 (>= 5.32.2) [hppa] <!profile.stage1>, dejagnu (>= 1.4.2-1.1), dpatch, file, bzip2:native, lsb-release
+Build-Depends: dpkg-dev (>= 1.13.9), autoconf (>= 2.13), bash, bison:amd64, flex, gettext:any, texinfo <!profile.stage1> <!profile.stage2 !profile.cross>, expect-tcl8.3 (>= 5.32.2) [hppa] <!profile.stage1>, dejagnu (>= 1.4.2-1.1), dpatch, file, bzip2:native, lsb-release
Architecture: any
Standards-Version: 3.7.3
Format: 1.0
diff --git a/tests/test_deb822.py b/tests/test_deb822.py
index f12dbfd..abf7fa1 100755
--- a/tests/test_deb822.py
+++ b/tests/test_deb822.py
@@ -1050,9 +1050,11 @@ class TestPkgRelations(unittest.TestCase):
f.close()
bin_rels = ['file, libc6 (>= 2.7-1), libpaper1, psutils']
- src_rels = ['apache2-src (>= 2.2.9), libaprutil1-dev, ' \
- 'libcap-dev [!kfreebsd-i386 !kfreebsd-amd64 !hurd-i386], ' \
- 'autoconf, debhelper (>> 5.0.0)']
+ src_rels = ['apache2-src (>= 2.2.9), libaprutil1-dev, '
+ 'libcap-dev [!kfreebsd-i386 !kfreebsd-amd64 !hurd-i386], '
+ 'autoconf <!profile.cross>, '
+ 'debhelper (>> 5.0.0) '
+ '<!profile.stage1> <!profile.cross !profile.stage2>']
for bin_rel in bin_rels:
self.assertEqual(bin_rel,
deb822.PkgRelation.str(deb822.PkgRelation.parse_relations(
@@ -1098,10 +1100,14 @@ class TestPkgRelations(unittest.TestCase):
[rel({'name': 'flex'})],
[rel({'name': 'gettext', 'archqual': 'any'})],
[rel({'name': 'texinfo',
- 'restrictions': [(False, ('profile', 'stage1')), (False, ('profile', 'cross'))]})],
+ 'restrictions': [
+ [(False, 'profile', 'stage1')],
+ [(False, 'profile', 'stage2'),
+ (False, 'profile', 'cross')]
+ ]})],
[rel({'arch': [(True, 'hppa')], 'name': 'expect-tcl8.3',
'version': ('>=', '5.32.2'),
- 'restrictions': [(False, ('profile', 'stage1'))]})],
+ 'restrictions': [[(False, 'profile', 'stage1')]]})],
[rel({'name': 'dejagnu', 'version': ('>=', '1.4.2-1.1'), 'arch': None})],
[rel({'name': 'dpatch'})],
[rel({'name': 'file'})],
@@ -1122,6 +1128,36 @@ class TestPkgRelations(unittest.TestCase):
self.assertPkgDictEqual(rel2, pkg2.relations)
f.close()
+ def test_restrictions_parse(self):
+ r = "foo <profile.cross>"
+ # relation 0, alternative 0, restrictions set 0, condition 0
+ rel = deb822.PkgRelation.parse_relations(r)[0][0]['restrictions'][0][0]
+ self.assertEqual(rel.enabled, True)
+ self.assertEqual(rel[0], True)
+ self.assertEqual(rel.namespace, 'profile')
+ self.assertEqual(rel[1], 'profile')
+ self.assertEqual(rel.label, 'cross')
+ self.assertEqual(rel[2], 'cross')
+
+ r = "foo <!profile.stage1> <!profile.stage2 !profile.cross>"
+ # relation 0, alternative 0, restrictions set 1, condition 0
+ rel = deb822.PkgRelation.parse_relations(r)[0][0]['restrictions'][1][0]
+ self.assertEqual(rel.enabled, False)
+ self.assertEqual(rel[0], False)
+ self.assertEqual(rel.namespace, 'profile')
+ self.assertEqual(rel[1], 'profile')
+ self.assertEqual(rel.label, 'stage2')
+ self.assertEqual(rel[2], 'stage2')
+
+ # relation 0, alternative 0, restrictions set 1, condition 1
+ rel = deb822.PkgRelation.parse_relations(r)[0][0]['restrictions'][1][1]
+ self.assertEqual(rel.enabled, False)
+ self.assertEqual(rel[0], False)
+ self.assertEqual(rel.namespace, 'profile')
+ self.assertEqual(rel[1], 'profile')
+ self.assertEqual(rel.label, 'cross')
+ self.assertEqual(rel[2], 'cross')
+
class TestGpgInfo(unittest.TestCase):
--
1.9.1
--
http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-python-debian-maint