From 092c45c924c6abd082004c0fc61d4eb2891c951e Mon Sep 17 00:00:00 2001 From: David Lai Date: Mon, 1 Mar 2021 09:24:04 +0800 Subject: [PATCH 1/8] add EphemeralsBinding.get_range --- src/rez/rex_bindings.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/rez/rex_bindings.py b/src/rez/rex_bindings.py index 750b3110b..df6d072ca 100644 --- a/src/rez/rex_bindings.py +++ b/src/rez/rex_bindings.py @@ -193,6 +193,14 @@ def __init__(self, ephemerals): def _attr_error(self, attr): raise AttributeError("ephemeral does not exist: '%s'" % attr) + def get_range(self, name, default=None): + """Returns ephemeral version range object""" + req_str = self._data.get(name) + if req_str: + return Requirement(req_str).range + else: + return VersionRange(default) + def intersects(obj, range_): """Test if an object intersects with the given version range. @@ -235,6 +243,10 @@ def commands(): return False range2 = req.range + # eg 'if intersects(ephemerals.get_range('foo.cli', '1'), ...)' + elif isinstance(obj, VersionRange): + range2 = obj + # eg 'if intersects(resolve.maya, ...)' elif isinstance(obj, VariantBinding): range2 = VersionRange(str(obj.version)) From 3e28baa5417c62cb713f08b47ef28d126d703336 Mon Sep 17 00:00:00 2001 From: David Lai Date: Wed, 3 Mar 2021 23:05:16 +0800 Subject: [PATCH 2/8] add RequirementsBinding.get_range --- src/rez/rex_bindings.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/rez/rex_bindings.py b/src/rez/rex_bindings.py index df6d072ca..790d9a011 100644 --- a/src/rez/rex_bindings.py +++ b/src/rez/rex_bindings.py @@ -173,6 +173,14 @@ def __init__(self, requirements): def _attr_error(self, attr): raise AttributeError("request does not exist: '%s'" % attr) + def get_range(self, name, default=None): + """Returns requirement version range object""" + req_str = self._data.get(name) + if req_str: + return Requirement(req_str).range + else: + return VersionRange(default) + class EphemeralsBinding(RO_MappingBinding): """Binds a list of resolved ephemeral packages. From 2c253c35ddd140d8da17ad0ef1aaae173de7dcaa Mon Sep 17 00:00:00 2001 From: David Lai Date: Wed, 3 Mar 2021 23:22:42 +0800 Subject: [PATCH 3/8] add test cases --- src/rez/tests/test_rex.py | 99 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/src/rez/tests/test_rex.py b/src/rez/tests/test_rex.py index 814e701db..4a120e779 100644 --- a/src/rez/tests/test_rex.py +++ b/src/rez/tests/test_rex.py @@ -4,13 +4,17 @@ from rez.rex import RexExecutor, Python, Setenv, Appendenv, Prependenv, Info, \ Comment, Alias, Command, Source, Error, Shebang, Unsetenv, expandable, \ literal -from rez.rex_bindings import VersionBinding +from rez.rex_bindings import VersionBinding, VariantsBinding, \ + RequirementsBinding, EphemeralsBinding, intersects from rez.exceptions import RexError, RexUndefinedVariableError from rez.config import config import unittest from rez.vendor.version.version import Version +from rez.vendor.version.requirement import Requirement from rez.tests.util import TestBase from rez.utils.backcompat import convert_old_commands +from rez.package_repository import package_repository_manager +from rez.packages import iter_package_families import inspect import textwrap import os @@ -407,6 +411,99 @@ def test_old_style_commands(self): annotate=False) self.assertEqual(rez_commands, expected) + def test_intersects_resolve(self): + """Test intersects with resolve object""" + resolved_pkg_data = { + "foo": {"1": {"name": "foo", "version": "1"}}, + "maya": {"2020.1": {"name": "maya", "version": "2020.1"}}, + } + mem_path = "memory@%s" % hex(id(resolved_pkg_data)) + resolved_repo = package_repository_manager.get_repository(mem_path) + resolved_repo.data = resolved_pkg_data + resolved_packages = [ + variant + for family in iter_package_families(paths=[mem_path]) + for package in family.iter_packages() + for variant in package.iter_variants() + ] + resolve = VariantsBinding(resolved_packages) + self.assertTrue(intersects(resolve.foo, "1")) + self.assertFalse(intersects(resolve.foo, "0")) + self.assertTrue(intersects(resolve.maya, "2019+")) + self.assertFalse(intersects(resolve.maya, "<=2019")) + + def test_intersects_request(self): + """Test intersects with request object""" + # request.get + request = RequirementsBinding([Requirement("foo.bar-1")]) + bar_on = intersects(request.get("foo.bar", "0"), "1") + self.assertTrue(bar_on) + + request = RequirementsBinding([]) + bar_on = intersects(request.get("foo.bar", "0"), "1") + self.assertTrue(bar_on) # should be False, but for backward compat + + request = RequirementsBinding([]) + bar_on = intersects(request.get("foo.bar", "foo.bar-0"), "1") + self.assertFalse(bar_on) # workaround, see PR nerdvegas/rez#1030 + + # request.get_range + request = RequirementsBinding([Requirement("foo.bar-1")]) + bar_on = intersects(request.get_range("foo.bar", "0"), "1") + self.assertTrue(bar_on) + + request = RequirementsBinding([]) + bar_on = intersects(request.get_range("foo.bar", "0"), "1") + self.assertFalse(bar_on) + + request = RequirementsBinding([]) + foo = intersects(request.get_range("foo", "==1.2.3"), "1.2") + self.assertTrue(foo) + + request = RequirementsBinding([]) + foo = intersects(request.get_range("foo", "==1.2.3"), "1.4") + self.assertFalse(foo) + + request = RequirementsBinding([Requirement("foo-1.4.5")]) + foo = intersects(request.get_range("foo", "==1.2.3"), "1.4") + self.assertTrue(foo) + + def test_intersects_ephemerals(self): + """Test intersects with ephemerals object""" + # ephemerals.get + ephemerals = EphemeralsBinding([Requirement(".foo.bar-1")]) + bar_on = intersects(ephemerals.get("foo.bar", "0"), "1") + self.assertTrue(bar_on) + + ephemerals = EphemeralsBinding([]) + bar_on = intersects(ephemerals.get("foo.bar", "0"), "1") + self.assertTrue(bar_on) # should be False, but for backward compat + + ephemerals = EphemeralsBinding([]) + bar_on = intersects(ephemerals.get("foo.bar", "foo.bar-0"), "1") + self.assertFalse(bar_on) # workaround, see PR nerdvegas/rez#1030 + + # ephemerals.get_range + ephemerals = EphemeralsBinding([Requirement(".foo.bar-1")]) + bar_on = intersects(ephemerals.get_range("foo.bar", "0"), "1") + self.assertTrue(bar_on) + + ephemerals = EphemeralsBinding([]) + bar_on = intersects(ephemerals.get_range("foo.bar", "0"), "1") + self.assertFalse(bar_on) + + ephemerals = EphemeralsBinding([]) + foo = intersects(ephemerals.get_range("foo", "==1.2.3"), "1.2") + self.assertTrue(foo) + + ephemerals = EphemeralsBinding([]) + foo = intersects(ephemerals.get_range("foo", "==1.2.3"), "1.4") + self.assertFalse(foo) + + ephemerals = EphemeralsBinding([Requirement(".foo-1.4.5")]) + foo = intersects(ephemerals.get_range("foo", "==1.2.3"), "1.4") + self.assertTrue(foo) + if __name__ == '__main__': unittest.main() From e551be4ff7382d9317bb00292896ee658ac44d0d Mon Sep 17 00:00:00 2001 From: David Lai Date: Thu, 4 Mar 2021 23:42:50 +0800 Subject: [PATCH 4/8] update package-commands wiki --- wiki/pages/Package-Commands.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/wiki/pages/Package-Commands.md b/wiki/pages/Package-Commands.md index 54be75056..f3f4c0eea 100644 --- a/wiki/pages/Package-Commands.md +++ b/wiki/pages/Package-Commands.md @@ -346,14 +346,23 @@ like *env.append*, but prepends the environment variable instead. ### ephemerals *Dict-like object* - if intersects(ephemerals.get("foo.cli", "1"), "1"): - env.PATH.append("{rot}/bin") + if intersects(ephemerals.get("foo.cli", default="foo.cli-1"), "1"): + env.PATH.append("{root}/bin") A dict representing the list of ephemerals in the resolved environment. Each item is a string (the full request, eg `.foo.cli-1`), keyed by the ephemeral package name. Note that you do **not** include the leading `.` when getting items from the `ephemerals` object. +Noted that the default value of `ephemerals.get` method should be a full request string so the `intersects` +can work as expect. + +Alternatively, you can use `ephemerals.get_range` to get `VersionRange` object, which may provide +a bit intuitive way to interact with `intersects`. + + if intersects(ephemerals.get_range("foo.cli", "1"), "1"): + env.PATH.append("{root}/bin") + ### error *Function* @@ -433,6 +442,20 @@ This request would yield the following *request* object: "corelib": "!corelib-1.4.4" } +To use with `intersects`: + + if intersects(request.get("foo.cli", default="foo.cli-1"), "1"): + env.PATH.append("{root}/bin") + +Noted that the default value of `request.get` method should be a full request string so the `intersects` +can work as expect. + +Alternatively, you can use `request.get_range` to get `VersionRange` object, which may provide +a bit intuitive way to interact with `intersects`. + + if intersects(request.get_range("foo.cli", "1"), "1"): + env.PATH.append("{root}/bin") + > [[media/icons/info.png]] If multiple requests are present that refer to the same package, the request is combined ahead of time. In other words, if requests *foo-4+* and *foo-<6* were both present, the single request *foo-4+<6* would be present in the *request* object. From 489807e06bf40f46facf0ce23802a68f2cff8147 Mon Sep 17 00:00:00 2001 From: David Lai Date: Fri, 5 Mar 2021 00:03:49 +0800 Subject: [PATCH 5/8] update ephemeral-packages wiki --- wiki/pages/Ephemeral-Packages.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/wiki/pages/Ephemeral-Packages.md b/wiki/pages/Ephemeral-Packages.md index 15c3b0a66..d794fd57d 100644 --- a/wiki/pages/Ephemeral-Packages.md +++ b/wiki/pages/Ephemeral-Packages.md @@ -80,7 +80,7 @@ to the [resolve](Package-Commands#resolve) object. You would typically use the # in package.py def commands() - if intersects(ephemerals.get('enable_tracking', '0'), '1'): + if intersects(ephemerals.get_range('enable_tracking', '0'), '1'): env.TRACKING_ENABLED = 1 In this example, the given package would set the `TRACKING_ENABLED` environment @@ -88,6 +88,11 @@ variable if an ephemeral such as `.enable_tracking-1` (or `.enable_tracking-1.2+ etc) is present in the resolve. Note that the leading `.` is implied and not included when querying the `ephemerals` object. +> [[media/icons/warning.png]] Since `ephemerals` is a dict-like object, so it has +> a `get` function which will return a full request string if key exists. Hence, +> the default value should also be a full request string, not just a version range +> string like `'0'` in `get_range`. Or `intersects` may not work as expect. + ## Ephemeral Use Cases Why would you want to request packages that don't exist? There are two main use @@ -101,7 +106,7 @@ to packages in a resolve. For example, consider the following package definition name = 'bah' def commands(): - if intersects(ephemerals.get('bah.cli', '1'), '1'): + if intersects(ephemerals.get_range('bah.cli', '1'), '1'): env.PATH.append('{root}/bin') This package will disable its command line tools if an ephemeral like `.bah.cli-0` @@ -120,7 +125,7 @@ we introduce a `.cli` ephemeral that acts as a global whitelist: name = 'bah' def commands(): - if intersects(ephemerals.get('cli', ''), 'bah'): + if intersects(ephemerals.get_range('cli', ''), 'bah'): env.PATH.append('{root}/bin') Here, all packages' cli will be enabled if `.cli` is not specified, but if it is From c12a8b97f8329a4749bc6d29037dadd7c7bd4fe6 Mon Sep 17 00:00:00 2001 From: David Lai Date: Fri, 5 Mar 2021 00:11:00 +0800 Subject: [PATCH 6/8] handling default=None --- src/rez/rex_bindings.py | 4 ++++ src/rez/tests/test_rex.py | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/rez/rex_bindings.py b/src/rez/rex_bindings.py index 790d9a011..0e59f96f9 100644 --- a/src/rez/rex_bindings.py +++ b/src/rez/rex_bindings.py @@ -263,6 +263,10 @@ def commands(): elif isinstance(obj, VersionBinding): range2 = VersionRange(str(obj)) + # eg 'if intersects(ephemerals.get('foo.cli'), ...)' + elif obj is None: + range2 = VersionRange(obj) + else: raise RuntimeError( "Invalid type %s passed as first arg to 'intersects'" % type(obj) diff --git a/src/rez/tests/test_rex.py b/src/rez/tests/test_rex.py index 4a120e779..1cb3b602a 100644 --- a/src/rez/tests/test_rex.py +++ b/src/rez/tests/test_rex.py @@ -483,6 +483,10 @@ def test_intersects_ephemerals(self): bar_on = intersects(ephemerals.get("foo.bar", "foo.bar-0"), "1") self.assertFalse(bar_on) # workaround, see PR nerdvegas/rez#1030 + ephemerals = EphemeralsBinding([]) + bar_on = intersects(ephemerals.get("foo.bar", default=None), "0") + self.assertFalse(bar_on) + # ephemerals.get_range ephemerals = EphemeralsBinding([Requirement(".foo.bar-1")]) bar_on = intersects(ephemerals.get_range("foo.bar", "0"), "1") @@ -504,6 +508,10 @@ def test_intersects_ephemerals(self): foo = intersects(ephemerals.get_range("foo", "==1.2.3"), "1.4") self.assertTrue(foo) + ephemerals = EphemeralsBinding([]) + bar_on = intersects(ephemerals.get_range("foo.bar", default=None), "0") + self.assertFalse(bar_on) + if __name__ == '__main__': unittest.main() From 85263b087480b900f6fa560dcf856f17afc82307 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Tue, 9 Mar 2021 10:16:08 +0800 Subject: [PATCH 7/8] return None if a default isn't specified --- src/rez/rex_bindings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rez/rex_bindings.py b/src/rez/rex_bindings.py index 0e59f96f9..87a2bf581 100644 --- a/src/rez/rex_bindings.py +++ b/src/rez/rex_bindings.py @@ -178,7 +178,7 @@ def get_range(self, name, default=None): req_str = self._data.get(name) if req_str: return Requirement(req_str).range - else: + elif default is not None: return VersionRange(default) @@ -206,7 +206,7 @@ def get_range(self, name, default=None): req_str = self._data.get(name) if req_str: return Requirement(req_str).range - else: + elif default is not None: return VersionRange(default) From cb1664457bdc4ebd7960e47e651270ba3c0a308a Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Tue, 9 Mar 2021 10:24:39 +0800 Subject: [PATCH 8/8] drop handling None, it's invalid input --- src/rez/rex_bindings.py | 4 ---- src/rez/tests/test_rex.py | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/rez/rex_bindings.py b/src/rez/rex_bindings.py index 87a2bf581..7a1e84778 100644 --- a/src/rez/rex_bindings.py +++ b/src/rez/rex_bindings.py @@ -263,10 +263,6 @@ def commands(): elif isinstance(obj, VersionBinding): range2 = VersionRange(str(obj)) - # eg 'if intersects(ephemerals.get('foo.cli'), ...)' - elif obj is None: - range2 = VersionRange(obj) - else: raise RuntimeError( "Invalid type %s passed as first arg to 'intersects'" % type(obj) diff --git a/src/rez/tests/test_rex.py b/src/rez/tests/test_rex.py index 1cb3b602a..b2712da4b 100644 --- a/src/rez/tests/test_rex.py +++ b/src/rez/tests/test_rex.py @@ -484,8 +484,8 @@ def test_intersects_ephemerals(self): self.assertFalse(bar_on) # workaround, see PR nerdvegas/rez#1030 ephemerals = EphemeralsBinding([]) - bar_on = intersects(ephemerals.get("foo.bar", default=None), "0") - self.assertFalse(bar_on) + self.assertRaises(RuntimeError, # no default + intersects, ephemerals.get("foo.bar"), "0") # ephemerals.get_range ephemerals = EphemeralsBinding([Requirement(".foo.bar-1")]) @@ -509,8 +509,8 @@ def test_intersects_ephemerals(self): self.assertTrue(foo) ephemerals = EphemeralsBinding([]) - bar_on = intersects(ephemerals.get_range("foo.bar", default=None), "0") - self.assertFalse(bar_on) + self.assertRaises(RuntimeError, # no default + intersects, ephemerals.get_range("foo.bar"), "0") if __name__ == '__main__':