From 4c9c7ab65fbd2bef7e336c4eabcfbe9a4467509d Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Mon, 6 Jan 2014 15:18:15 +0100 Subject: Local specs now called "anonymous specs" - anonymous specs have no name - "local spec" came from these first being used in multimethods, i.e. "name of the local Package" - not the most intuitive name. --- lib/spack/spack/multimethod.py | 4 +- lib/spack/spack/packages/__init__.py | 8 +-- lib/spack/spack/relations.py | 4 +- lib/spack/spack/spec.py | 77 ++++++++++++++-------- lib/spack/spack/test/spec_semantics.py | 115 ++++++++++++++++++--------------- lib/spack/spack/test/spec_syntax.py | 10 +-- 6 files changed, 126 insertions(+), 92 deletions(-) (limited to 'lib') diff --git a/lib/spack/spack/multimethod.py b/lib/spack/spack/multimethod.py index dc948d2861..efc5f5a9e1 100644 --- a/lib/spack/spack/multimethod.py +++ b/lib/spack/spack/multimethod.py @@ -26,7 +26,7 @@ import collections import spack.architecture import spack.error from spack.util.lang import * -from spack.spec import parse_local_spec, Spec +from spack.spec import parse_anonymous_spec, Spec class SpecMultiMethod(object): @@ -179,7 +179,7 @@ class when(object): class when(object): def __init__(self, spec): pkg = get_calling_package_name() - self.spec = parse_local_spec(spec, pkg) + self.spec = parse_anonymous_spec(spec, pkg) def __call__(self, method): # Get the first definition of the method in the calling scope diff --git a/lib/spack/spack/packages/__init__.py b/lib/spack/spack/packages/__init__.py index d0e6b74f0a..a6dd4d1b5c 100644 --- a/lib/spack/spack/packages/__init__.py +++ b/lib/spack/spack/packages/__init__.py @@ -21,7 +21,7 @@ invalid_package_re = r'[_-][_-]+' instances = {} -def autospec(function): +def _autospec(function): """Decorator that automatically converts the argument of a single-arg function to a Spec.""" def converter(arg): @@ -147,7 +147,7 @@ class ProviderIndex(object): -@autospec +@_autospec def get(spec): if spec.virtual: raise UnknownPackageError(spec.name) @@ -159,12 +159,12 @@ def get(spec): return instances[spec.name] -@autospec +@_autospec def get_installed(spec): return [s for s in installed_package_specs() if s.satisfies(spec)] -@autospec +@_autospec def providers_for(vpkg_spec): if not hasattr(providers_for, 'index'): providers_for.index = ProviderIndex(all_package_names()) diff --git a/lib/spack/spack/relations.py b/lib/spack/spack/relations.py index 398f25429f..aaa1495c91 100644 --- a/lib/spack/spack/relations.py +++ b/lib/spack/spack/relations.py @@ -51,7 +51,7 @@ import importlib import spack import spack.spec import spack.error -from spack.spec import Spec, parse_local_spec +from spack.spec import Spec, parse_anonymous_spec from spack.packages import packages_module from spack.util.lang import * @@ -76,7 +76,7 @@ def provides(*specs, **kwargs): """ pkg = get_calling_package_name() spec_string = kwargs.get('when', pkg) - provider_spec = parse_local_spec(spec_string, pkg) + provider_spec = parse_anonymous_spec(spec_string, pkg) provided = caller_locals().setdefault("provided", {}) for string in specs: diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index f2c3d70f23..1657e2128b 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -739,6 +739,8 @@ class Spec(object): def constrain(self, other, **kwargs): + other = self._autospec(other) + if not self.name == other.name: raise UnsatisfiableSpecNameError(self.name, other.name) @@ -766,10 +768,10 @@ class Spec(object): self.architecture = self.architecture or other.architecture if kwargs.get('deps', True): - self.constrain_dependencies(other) + self._constrain_dependencies(other) - def constrain_dependencies(self, other): + def _constrain_dependencies(self, other): """Apply constraints of other spec's dependencies to this spec.""" if not self.dependencies or not other.dependencies: return @@ -806,9 +808,22 @@ class Spec(object): return mine + def _autospec(self, spec_like): + """Used to convert arguments to specs. If spec_like is a spec, returns it. + If it's a string, tries to parse a string. If that fails, tries to parse + a local spec from it (i.e. name is assumed to be self's name). + """ + if isinstance(spec_like, spack.spec.Spec): + return spec_like + + try: + return spack.spec.Spec(spec_like) + except SpecError: + return parse_anonymous_spec(spec_like, self.name) + + def satisfies(self, other, **kwargs): - if not isinstance(other, Spec): - other = Spec(other) + other = self._autospec(other) # First thing we care about is whether the name matches if self.name != other.name: @@ -934,9 +949,7 @@ class Spec(object): def __contains__(self, spec): """True if this spec has any dependency that satisfies the supplied spec.""" - if not isinstance(spec, Spec): - spec = Spec(spec) - + spec = self._autospec(spec) for s in self.preorder_traversal(): if s.satisfies(spec): return True @@ -1104,18 +1117,22 @@ class SpecParser(spack.parse.Parser): def do_parse(self): specs = [] - while self.next: - if self.accept(ID): - specs.append(self.spec()) - elif self.accept(DEP): - if not specs: - self.last_token_error("Dependency has no package") - self.expect(ID) - specs[-1]._add_dependency(self.spec()) + try: + while self.next: + if self.accept(ID): + specs.append(self.spec()) - else: - self.unexpected_token() + elif self.accept(DEP): + if not specs: + self.last_token_error("Dependency has no package") + self.expect(ID) + specs[-1]._add_dependency(self.spec()) + + else: + self.unexpected_token() + except spack.parse.ParseError, e: + raise SpecParseError(e) return specs @@ -1238,7 +1255,7 @@ def parse(string): return SpecParser().parse(string) -def parse_local_spec(spec_like, pkg_name): +def parse_anonymous_spec(spec_like, pkg_name): """Allow the user to omit the package name part of a spec if they know what it has to be already. @@ -1251,19 +1268,19 @@ def parse_local_spec(spec_like, pkg_name): if isinstance(spec_like, str): try: - local_spec = Spec(spec_like) - except spack.parse.ParseError: - local_spec = Spec(pkg_name + spec_like) - if local_spec.name != pkg_name: raise ValueError( + anon_spec = Spec(spec_like) + except SpecParseError: + anon_spec = Spec(pkg_name + spec_like) + if anon_spec.name != pkg_name: raise ValueError( "Invalid spec for package %s: %s" % (pkg_name, spec_like)) else: - local_spec = spec_like + anon_spec = spec_like.copy() - if local_spec.name != pkg_name: + if anon_spec.name != pkg_name: raise ValueError("Spec name '%s' must match package name '%s'" - % (local_spec.name, pkg_name)) + % (anon_spec.name, pkg_name)) - return local_spec + return anon_spec class SpecError(spack.error.SpackError): @@ -1272,6 +1289,14 @@ class SpecError(spack.error.SpackError): super(SpecError, self).__init__(message) +class SpecParseError(SpecError): + """Wrapper for ParseError for when we're parsing specs.""" + def __init__(self, parse_error): + super(SpecParseError, self).__init__(parse_error.message) + self.string = parse_error.string + self.pos = parse_error.pos + + class DuplicateDependencyError(SpecError): """Raised when the same dependency occurs in a spec twice.""" def __init__(self, message): diff --git a/lib/spack/spack/test/spec_semantics.py b/lib/spack/spack/test/spec_semantics.py index d1cd5ca1c5..a0ded3b30c 100644 --- a/lib/spack/spack/test/spec_semantics.py +++ b/lib/spack/spack/test/spec_semantics.py @@ -9,25 +9,34 @@ class SpecSematicsTest(MockPackagesTest): # ================================================================================ # Utility functions to set everything up. # ================================================================================ - def check_satisfies(self, lspec, rspec): - l, r = Spec(lspec), Spec(rspec) - self.assertTrue(l.satisfies(r)) - self.assertTrue(r.satisfies(l)) + def check_satisfies(self, spec, anon_spec): + left = Spec(spec) + right = parse_anonymous_spec(anon_spec, left.name) + + self.assertTrue(left.satisfies(right)) + self.assertTrue(left.satisfies(anon_spec)) + self.assertTrue(right.satisfies(left)) try: - l.constrain(r) - r.constrain(l) + left.copy().constrain(right) + left.copy().constrain(anon_spec) + right.copy().constrain(left) except SpecError, e: self.fail("Got a SpecError in constrain! " + e.message) - def check_unsatisfiable(self, lspec, rspec): - l, r = Spec(lspec), Spec(rspec) - self.assertFalse(l.satisfies(r)) - self.assertFalse(r.satisfies(l)) + def check_unsatisfiable(self, spec, anon_spec): + left = Spec(spec) + right = parse_anonymous_spec(anon_spec, left.name) + + self.assertFalse(left.satisfies(right)) + self.assertFalse(left.satisfies(anon_spec)) + + self.assertFalse(right.satisfies(left)) - self.assertRaises(UnsatisfiableSpecError, l.constrain, r) - self.assertRaises(UnsatisfiableSpecError, r.constrain, l) + self.assertRaises(UnsatisfiableSpecError, left.constrain, right) + self.assertRaises(UnsatisfiableSpecError, left.constrain, anon_spec) + self.assertRaises(UnsatisfiableSpecError, right.constrain, left) def check_constrain(self, expected, constrained, constraint): @@ -48,77 +57,77 @@ class SpecSematicsTest(MockPackagesTest): # Satisfiability and constraints # ================================================================================ def test_satisfies(self): - self.check_satisfies('libelf@0.8.13', 'libelf@0:1') - self.check_satisfies('libdwarf^libelf@0.8.13', 'libdwarf^libelf@0:1') + self.check_satisfies('libelf@0.8.13', '@0:1') + self.check_satisfies('libdwarf^libelf@0.8.13', '^libelf@0:1') def test_satisfies_compiler(self): - self.check_satisfies('foo%gcc', 'foo%gcc') - self.check_satisfies('foo%intel', 'foo%intel') - self.check_unsatisfiable('foo%intel', 'foo%gcc') - self.check_unsatisfiable('foo%intel', 'foo%pgi') + self.check_satisfies('foo%gcc', '%gcc') + self.check_satisfies('foo%intel', '%intel') + self.check_unsatisfiable('foo%intel', '%gcc') + self.check_unsatisfiable('foo%intel', '%pgi') def test_satisfies_compiler_version(self): - self.check_satisfies('foo%gcc', 'foo%gcc@4.7.2') - self.check_satisfies('foo%intel', 'foo%intel@4.7.2') + self.check_satisfies('foo%gcc', '%gcc@4.7.2') + self.check_satisfies('foo%intel', '%intel@4.7.2') - self.check_satisfies('foo%pgi@4.5', 'foo%pgi@4.4:4.6') - self.check_satisfies('foo@2.0%pgi@4.5', 'foo@1:3%pgi@4.4:4.6') + self.check_satisfies('foo%pgi@4.5', '%pgi@4.4:4.6') + self.check_satisfies('foo@2.0%pgi@4.5', '@1:3%pgi@4.4:4.6') - self.check_unsatisfiable('foo%pgi@4.3', 'foo%pgi@4.4:4.6') - self.check_unsatisfiable('foo@4.0%pgi', 'foo@1:3%pgi') - self.check_unsatisfiable('foo@4.0%pgi@4.5', 'foo@1:3%pgi@4.4:4.6') + self.check_unsatisfiable('foo%pgi@4.3', '%pgi@4.4:4.6') + self.check_unsatisfiable('foo@4.0%pgi', '@1:3%pgi') + self.check_unsatisfiable('foo@4.0%pgi@4.5', '@1:3%pgi@4.4:4.6') def test_satisfies_architecture(self): - self.check_satisfies('foo=chaos_5_x86_64_ib', 'foo=chaos_5_x86_64_ib') - self.check_satisfies('foo=bgqos_0', 'foo=bgqos_0') + self.check_satisfies('foo=chaos_5_x86_64_ib', '=chaos_5_x86_64_ib') + self.check_satisfies('foo=bgqos_0', '=bgqos_0') - self.check_unsatisfiable('foo=bgqos_0', 'foo=chaos_5_x86_64_ib') - self.check_unsatisfiable('foo=chaos_5_x86_64_ib', 'foo=bgqos_0') + self.check_unsatisfiable('foo=bgqos_0', '=chaos_5_x86_64_ib') + self.check_unsatisfiable('foo=chaos_5_x86_64_ib', '=bgqos_0') def test_satisfies_dependencies(self): - self.check_satisfies('mpileaks^mpich', 'mpileaks^mpich') - self.check_satisfies('mpileaks^zmpi', 'mpileaks^zmpi') + self.check_satisfies('mpileaks^mpich', '^mpich') + self.check_satisfies('mpileaks^zmpi', '^zmpi') - self.check_unsatisfiable('mpileaks^mpich', 'mpileaks^zmpi') - self.check_unsatisfiable('mpileaks^zmpi', 'mpileaks^mpich') + self.check_unsatisfiable('mpileaks^mpich', '^zmpi') + self.check_unsatisfiable('mpileaks^zmpi', '^mpich') def test_satisfies_dependency_versions(self): - self.check_satisfies('mpileaks^mpich@2.0', 'mpileaks^mpich@1:3') - self.check_unsatisfiable('mpileaks^mpich@1.2', 'mpileaks^mpich@2.0') + self.check_satisfies('mpileaks^mpich@2.0', '^mpich@1:3') + self.check_unsatisfiable('mpileaks^mpich@1.2', '^mpich@2.0') - self.check_satisfies('mpileaks^mpich@2.0^callpath@1.5', 'mpileaks^mpich@1:3^callpath@1.4:1.6') - self.check_unsatisfiable('mpileaks^mpich@4.0^callpath@1.5', 'mpileaks^mpich@1:3^callpath@1.4:1.6') - self.check_unsatisfiable('mpileaks^mpich@2.0^callpath@1.7', 'mpileaks^mpich@1:3^callpath@1.4:1.6') - self.check_unsatisfiable('mpileaks^mpich@4.0^callpath@1.7', 'mpileaks^mpich@1:3^callpath@1.4:1.6') + self.check_satisfies('mpileaks^mpich@2.0^callpath@1.5', '^mpich@1:3^callpath@1.4:1.6') + self.check_unsatisfiable('mpileaks^mpich@4.0^callpath@1.5', '^mpich@1:3^callpath@1.4:1.6') + self.check_unsatisfiable('mpileaks^mpich@2.0^callpath@1.7', '^mpich@1:3^callpath@1.4:1.6') + self.check_unsatisfiable('mpileaks^mpich@4.0^callpath@1.7', '^mpich@1:3^callpath@1.4:1.6') def test_satisfies_virtual_dependencies(self): - self.check_satisfies('mpileaks^mpi', 'mpileaks^mpi') - self.check_satisfies('mpileaks^mpi', 'mpileaks^mpich') + self.check_satisfies('mpileaks^mpi', '^mpi') + self.check_satisfies('mpileaks^mpi', '^mpich') - self.check_satisfies('mpileaks^mpi', 'mpileaks^zmpi') - self.check_unsatisfiable('mpileaks^mpich', 'mpileaks^zmpi') + self.check_satisfies('mpileaks^mpi', '^zmpi') + self.check_unsatisfiable('mpileaks^mpich', '^zmpi') def test_satisfies_virtual_dependency_versions(self): - self.check_satisfies('mpileaks^mpi@1.5', 'mpileaks^mpi@1.2:1.6') - self.check_unsatisfiable('mpileaks^mpi@3', 'mpileaks^mpi@1.2:1.6') + self.check_satisfies('mpileaks^mpi@1.5', '^mpi@1.2:1.6') + self.check_unsatisfiable('mpileaks^mpi@3', '^mpi@1.2:1.6') - self.check_satisfies('mpileaks^mpi@2:', 'mpileaks^mpich') - self.check_satisfies('mpileaks^mpi@2:', 'mpileaks^mpich@3.0.4') - self.check_satisfies('mpileaks^mpi@2:', 'mpileaks^mpich2@1.4') + self.check_satisfies('mpileaks^mpi@2:', '^mpich') + self.check_satisfies('mpileaks^mpi@2:', '^mpich@3.0.4') + self.check_satisfies('mpileaks^mpi@2:', '^mpich2@1.4') - self.check_satisfies('mpileaks^mpi@1:', 'mpileaks^mpich2') - self.check_satisfies('mpileaks^mpi@2:', 'mpileaks^mpich2') + self.check_satisfies('mpileaks^mpi@1:', '^mpich2') + self.check_satisfies('mpileaks^mpi@2:', '^mpich2') - self.check_unsatisfiable('mpileaks^mpi@3:', 'mpileaks^mpich2@1.4') - self.check_unsatisfiable('mpileaks^mpi@3:', 'mpileaks^mpich2') - self.check_unsatisfiable('mpileaks^mpi@3:', 'mpileaks^mpich@1.0') + self.check_unsatisfiable('mpileaks^mpi@3:', '^mpich2@1.4') + self.check_unsatisfiable('mpileaks^mpi@3:', '^mpich2') + self.check_unsatisfiable('mpileaks^mpi@3:', '^mpich@1.0') def test_constrain(self): diff --git a/lib/spack/spack/test/spec_syntax.py b/lib/spack/spack/test/spec_syntax.py index 4079650246..ec6761015e 100644 --- a/lib/spack/spack/test/spec_syntax.py +++ b/lib/spack/spack/test/spec_syntax.py @@ -1,7 +1,7 @@ import unittest import spack.spec from spack.spec import * -from spack.parse import Token, ParseError +from spack.parse import Token # Sample output for a complex lexing. complex_lex = [Token(ID, 'mvapich_foo'), @@ -96,10 +96,10 @@ class SpecSyntaxTest(unittest.TestCase): self.check_parse("x^y", "x@: ^y@:") def test_parse_errors(self): - self.assertRaises(ParseError, self.check_parse, "x@@1.2") - self.assertRaises(ParseError, self.check_parse, "x ^y@@1.2") - self.assertRaises(ParseError, self.check_parse, "x@1.2::") - self.assertRaises(ParseError, self.check_parse, "x::") + self.assertRaises(SpecParseError, self.check_parse, "x@@1.2") + self.assertRaises(SpecParseError, self.check_parse, "x ^y@@1.2") + self.assertRaises(SpecParseError, self.check_parse, "x@1.2::") + self.assertRaises(SpecParseError, self.check_parse, "x::") def test_duplicate_variant(self): self.assertRaises(DuplicateVariantError, self.check_parse, "x@1.2+debug+debug") -- cgit v1.2.3-60-g2f50