diff options
-rw-r--r-- | lib/spack/docs/developer_guide.rst | 2 | ||||
-rw-r--r-- | lib/spack/docs/packaging_guide.rst | 6 | ||||
-rw-r--r-- | lib/spack/spack/multimethod.py | 51 | ||||
-rw-r--r-- | lib/spack/spack/test/mock_packages/multimethod.py | 25 | ||||
-rw-r--r-- | lib/spack/spack/test/multimethod.py | 33 |
5 files changed, 71 insertions, 46 deletions
diff --git a/lib/spack/docs/developer_guide.rst b/lib/spack/docs/developer_guide.rst index eca5accb15..47b98a211d 100644 --- a/lib/spack/docs/developer_guide.rst +++ b/lib/spack/docs/developer_guide.rst @@ -3,6 +3,8 @@ Developer Guide ===================== +This guide is intended for people who want to work on Spack's inner +workings. Right now it's pretty sparse. Spec objects ------------------------- diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst index 55f7391cd9..75e738efcd 100644 --- a/lib/spack/docs/packaging_guide.rst +++ b/lib/spack/docs/packaging_guide.rst @@ -1060,17 +1060,17 @@ for example: # the default, called when no @when specs match pass - @when('mpi@3:') + @when('^mpi@3:') def setup_mpi(self): # this will be called when mpi is version 3 or higher pass - @when('mpi@2:') + @when('^mpi@2:') def setup_mpi(self): # this will be called when mpi is version 2 or higher pass - @when('mpi@1:') + @when('^mpi@1:') def setup_mpi(self): # this will be called when mpi is version 1 or higher pass diff --git a/lib/spack/spack/multimethod.py b/lib/spack/spack/multimethod.py index f4de6445da..46ddb90d32 100644 --- a/lib/spack/spack/multimethod.py +++ b/lib/spack/spack/multimethod.py @@ -52,20 +52,20 @@ class SpecMultiMethod(object): registers method versions with them. To register a method, you can do something like this: - mf = SpecMultiMethod() - mf.register("^chaos_5_x86_64_ib", some_method) + mm = SpecMultiMethod() + mm.register("^chaos_5_x86_64_ib", some_method) The object registered needs to be a Spec or some string that will parse to be a valid spec. - When the pmf is actually called, it selects a version of the + When the mm is actually called, it selects a version of the method to call based on the sys_type of the object it is called on. See the docs for decorators below for more details. """ def __init__(self, default=None): - self.method_map = {} + self.method_list = [] self.default = default if default: functools.update_wrapper(self, default) @@ -73,7 +73,7 @@ class SpecMultiMethod(object): def register(self, spec, method): """Register a version of a method for a particular sys_type.""" - self.method_map[spec] = method + self.method_list.append((spec, method)) if not hasattr(self, '__name__'): functools.update_wrapper(self, method) @@ -87,33 +87,25 @@ class SpecMultiMethod(object): def __call__(self, package_self, *args, **kwargs): - """Try to find a method that matches package_self.sys_type. - If none is found, call the default method that this was - initialized with. If there is no default, raise an error. + """Find the first method with a spec that matches the + package's spec. If none is found, call the default + or if there is none, then raise a NoSuchMethodError. """ - spec = package_self.spec - matching_specs = [s for s in self.method_map if s.satisfies(spec)] - num_matches = len(matching_specs) - if num_matches == 0: - if self.default is None: - raise NoSuchMethodError(type(package_self), self.__name__, - spec, self.method_map.keys()) - else: - method = self.default - - elif num_matches == 1: - method = self.method_map[matching_specs[0]] + for spec, method in self.method_list: + if spec.satisfies(package_self.spec): + return method(package_self, *args, **kwargs) + if self.default: + return self.default(package_self, *args, **kwargs) else: - raise AmbiguousMethodError(type(package_self), self.__name__, - spec, matching_specs) - - return method(package_self, *args, **kwargs) + raise NoSuchMethodError( + type(package_self), self.__name__, spec, + [m[0] for m in self.method_list]) def __str__(self): return "SpecMultiMethod {\n\tdefault: %s,\n\tspecs: %s\n}" % ( - self.default, self.method_map) + self.default, self.method_list) class when(object): @@ -207,12 +199,3 @@ class NoSuchMethodError(spack.error.SpackError): "Package %s does not support %s called with %s. Options are: %s" % (cls.__name__, method_name, spec, ", ".join(str(s) for s in possible_specs))) - - -class AmbiguousMethodError(spack.error.SpackError): - """Raised when we can't find a version of a multi-method.""" - def __init__(self, cls, method_name, spec, matching_specs): - super(AmbiguousMethodError, self).__init__( - "Package %s has multiple versions of %s that match %s: %s" - % (cls.__name__, method_name, spec, - ",".join(str(s) for s in matching_specs))) diff --git a/lib/spack/spack/test/mock_packages/multimethod.py b/lib/spack/spack/test/mock_packages/multimethod.py index e5c2f5243e..6b6aca9767 100644 --- a/lib/spack/spack/test/mock_packages/multimethod.py +++ b/lib/spack/spack/test/mock_packages/multimethod.py @@ -27,16 +27,35 @@ class Multimethod(Package): # - # These functions overlap too much, so there is ambiguity + # These functions overlap, so there is ambiguity, but we'll take + # the first one. # @when('@:4') def version_overlap(self): - pass + return 1 @when('@2:') def version_overlap(self): - pass + return 2 + + + # + # More complicated case with cascading versions. + # + def mpi_version(self): + return 0 + @when('^mpi@3:') + def mpi_version(self): + return 3 + + @when('^mpi@2:') + def mpi_version(self): + return 2 + + @when('^mpi@1:') + def mpi_version(self): + return 1 # diff --git a/lib/spack/spack/test/multimethod.py b/lib/spack/spack/test/multimethod.py index 969c987da8..3a164a3d20 100644 --- a/lib/spack/spack/test/multimethod.py +++ b/lib/spack/spack/test/multimethod.py @@ -30,8 +30,30 @@ class MultiMethodTest(MockPackagesTest): def test_version_overlap(self): - pkg = packages.get('multimethod@3.0') - self.assertRaises(AmbiguousMethodError, pkg.version_overlap) + pkg = packages.get('multimethod@2.0') + self.assertEqual(pkg.version_overlap(), 1) + + pkg = packages.get('multimethod@5.0') + self.assertEqual(pkg.version_overlap(), 2) + + + def test_mpi_version(self): + pkg = packages.get('multimethod^mpich@3.0.4') + self.assertEqual(pkg.mpi_version(), 3) + + pkg = packages.get('multimethod^mpich2@1.2') + self.assertEqual(pkg.mpi_version(), 2) + + pkg = packages.get('multimethod^mpich@1.0') + self.assertEqual(pkg.mpi_version(), 1) + + + def test_undefined_mpi_version(self): + # This currently fails because provides() doesn't do + # the right thing undefined version ranges. + # TODO: fix this. + pkg = packages.get('multimethod^mpich@0.4') + self.assertEqual(pkg.mpi_version(), 0) def test_default_works(self): @@ -69,11 +91,10 @@ class MultiMethodTest(MockPackagesTest): pkg = packages.get('multimethod^mpich') self.assertEqual(pkg.different_by_dep(), 'mpich') - - def test_ambiguous_dep(self): - """If we try to switch on some entirely different dep, it's ambiguous""" + # If we try to switch on some entirely different dep, it's ambiguous, + # but should take the first option pkg = packages.get('multimethod^foobar') - self.assertRaises(AmbiguousMethodError, pkg.different_by_dep) + self.assertEqual(pkg.different_by_dep(), 'mpich') def test_virtual_dep_match(self): |