Add new syntax to allow matching multiple profile

Given the following:
package1 [foo bar]
package2 [foo (bar baz)]
package3 [baz]
package4 [(bar baz)]

Profile 'foo' will match package1 and package2
Profile 'bar' will match package1
Profile 'baz' will match package3
package4 cannot be match when only declaring a single profile

Specifing profiles 'bar baz' will match all four packages

Additionally, this relaxes the whitespace around the profiles and
groups. Previously while space was allowed after but not before braces.

Change-Id: I077943ff52cc8dc2eb6437925f8ca653b3534508
This commit is contained in:
Sam Yaple 2017-09-21 23:39:02 -04:00
parent 71c58d24c0
commit 24427065c5
5 changed files with 83 additions and 4 deletions

View File

@ -92,6 +92,12 @@ match on the other selectors. As an example, ``[platform:rpm test]``
would only install a package on a RPM platform if the test selector is
used.
Profiles can also be grouped together using ``()``. In a group, all profiles
must match for the group to match. Given the example
``[test (ceph glance !lvm)]``, to select the package you must either specify
``test`` OR (``ceph`` AND ``glance`` AND NOT ``lvm``). Platform selectors will
not work inside of the group.
Version constraints are a comma separated list of constraints where each
constraint is (== | < | <= | >= | > | !=) VERSION, and the constraints are ANDed
together (the same as pip requirements version constraints).
@ -160,3 +166,12 @@ To select the curl package, the OpenStack CI default file uses::
This selects the ``curl`` package on all distributions with the
exception of Gentoo, and selects ``net-misc/curl`` on Gentoo only.
To select a package based on a group of profiles::
ceph-common [ceph]
python-rbd [(ceph glance)]
This selects the ``ceph-common`` package when the profile ``ceph`` is
specified. However, it will only select the ``python-rbd`` package when both
``ceph`` and ``glance`` profiles are active.

View File

@ -47,7 +47,9 @@ lowercase = ('a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'|'k'|'l'|'m'|'n'|'o'|'p'
name = letterOrDigit:start (letterOrDigit|'.'|'+'|'-'|'_'|'/')+:rest
ws = ' '+
profile = ('!'?:neg <(lowercase|digit|':'|'-'|'.')+>:name) -> (neg!='!', name)
selector = ws '[' profile:p1 (ws profile)*:p2 ']' -> [p1] + p2
profiles = '(' (ws? profile)*:p ws? ')' -> p
group = profiles | profile
selector = ws '[' (ws? group)*:p ws? ']' -> p
oneversion = <('<=' | '<' | '!=' | '==' | '>=' | '>')>:rel <debversion>:v -> (
rel, v)
version = ws oneversion:v1 (',' oneversion)*:v2 -> [v1] + v2
@ -138,7 +140,11 @@ class Depends(object):
"""
platform = []
user = []
for sense, profile in rule[1]:
for group in rule[1]:
if isinstance(group, list):
user.append(group)
continue
sense, profile = group
if profile.startswith("platform:"):
platform.append((sense, profile))
else:
@ -160,7 +166,15 @@ class Depends(object):
positive = False
match_found = False
negative = False
for sense, profile in partition_rule:
for group in partition_rule:
if isinstance(group, list):
if self._match_all(group, profiles):
match_found = True
continue
else:
negative = True
break
sense, profile = group
if sense:
positive = True
if profile in profiles:
@ -173,6 +187,19 @@ class Depends(object):
return True
return False
def _match_all(self, partition_rules, profiles):
"""Evaluate rules. Do they all match the profiles?
:return Result True if all profiles match else False
"""
def matches(sense, profile, profiles):
return sense if profile in profiles else not sense
for sense, profile in partition_rules:
if not matches(sense, profile, profiles):
return False
return True
def active_rules(self, profiles):
"""Return the rules active given profiles.

View File

@ -27,7 +27,7 @@ logging.basicConfig(
def main(depends=None):
usage = "Usage: %prog [options] [profile]"
usage = "Usage: %prog [options] [profile]..."
parser = optparse.OptionParser(
usage=usage, version="%%prog %s" % bindep.version)
parser.add_option(

View File

@ -236,6 +236,34 @@ class TestDepends(TestCase):
[("foo", [(False, "bar"), (True, "baz"), (True, "quux")], [])],
depends._rules)
def test_whitespace(self):
depends = Depends("foo [ ( bar !baz ) quux ]\n")
self.assertEqual(
[("foo", [[(True, "bar"), (False, "baz")], (True, "quux")], [])],
depends._rules)
def test_group_selectors(self):
depends = Depends("foo [(bar !baz) quux]\n")
self.assertEqual(
[("foo", [[(True, "bar"), (False, "baz")], (True, "quux")], [])],
depends._rules)
def test_multiple_group_selectors(self):
depends = Depends("foo [(bar baz) (baz quux)]\n")
parsed_profiles = [
[(True, "bar"), (True, "baz")],
[(True, "baz"), (True, "quux")],
]
self.assertEqual(
[("foo", parsed_profiles, [])],
depends._rules)
def test_single_profile_group_selectors(self):
depends = Depends("foo [(bar) (!baz)]\n")
self.assertEqual(
[("foo", [[(True, "bar")], [(False, "baz")]], [])],
depends._rules)
def test_versions(self):
depends = Depends("foo <=1,!=2\n")
self.assertEqual(

View File

@ -0,0 +1,9 @@
---
features:
- |
New syntax extention allows declaring a group of profiles which must be
specified to enable the package. Using the syntax as follows
package1 [(profile1 profile2)]
To install 'package1' you must declare profile1 AND profile2