diff --git a/lib/spack/spack/cmd/__init__.py b/lib/spack/spack/cmd/__init__.py index 9b034b2a60..96d02f7855 100644 --- a/lib/spack/spack/cmd/__init__.py +++ b/lib/spack/spack/cmd/__init__.py @@ -61,7 +61,7 @@ def parse_specs(args): return spack.spec.parse(" ".join(args)) except spack.parse.ParseError, e: - e.print_error(sys.stdout) + tty.error(e.message, e.string, e.pos * " " + "^") sys.exit(1) except spack.spec.SpecError, e: diff --git a/lib/spack/spack/cmd/test.py b/lib/spack/spack/cmd/test.py index bc62798dda..93f38bafe8 100644 --- a/lib/spack/spack/cmd/test.py +++ b/lib/spack/spack/cmd/test.py @@ -24,10 +24,10 @@ def test(parser, args): spack.test.run(name, verbose=args.verbose) elif not args.names: - print parser._subparsers print "Available tests:" colify(list_modules(spack.test_path)) + else: for name in args.names: spack.test.run(name, verbose=args.verbose) diff --git a/lib/spack/spack/parse.py b/lib/spack/spack/parse.py index e34f833bf6..5bcdced6ca 100644 --- a/lib/spack/spack/parse.py +++ b/lib/spack/spack/parse.py @@ -1,6 +1,5 @@ import re import spack.error as err -import spack.tty as tty import itertools @@ -11,9 +10,6 @@ def __init__(self, message, string, pos): self.string = string self.pos = pos - def print_error(self, out): - tty.error(self.message, self.string, self.pos * " " + "^") - class LexError(ParseError): """Raised when we don't know how to lex something.""" diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index 86cab3430c..d46eb33201 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -42,8 +42,10 @@ spec-list = { spec [ dep-list ] } dep_list = { ^ spec } spec = id [ options ] - options = { @version-list | +variant | -variant | ~variant | %compiler } + options = { @version-list | +variant | -variant | ~variant | + %compiler | =architecture } variant = id + architecture = id compiler = id [ version-list ] version-list = version [ { , version } ] version = id | id: | :id | id:id @@ -59,11 +61,22 @@ specs to avoid ambiguity. Both are provided because ~ can cause shell expansion when it is the first character in an id typed on the command line. """ +import sys from functools import total_ordering +from StringIO import StringIO +import tty import spack.parse -from spack.version import Version, VersionRange import spack.error +from spack.version import Version, VersionRange +from spack.color import ColorStream + +# Color formats for various parts of specs when using color output. +compiler_fmt = '@g' +version_fmt = '@c' +architecture_fmt = '@m' +variant_enabled_fmt = '@B' +variant_disabled_fmt = '@r' class SpecError(spack.error.SpackError): @@ -86,6 +99,11 @@ class DuplicateCompilerError(SpecError): def __init__(self, message): super(DuplicateCompilerError, self).__init__(message) +class DuplicateArchitectureError(SpecError): + """Raised when the same architecture occurs in a spec twice.""" + def __init__(self, message): + super(DuplicateArchitectureError, self).__init__(message) + class Compiler(object): def __init__(self, name): @@ -95,19 +113,28 @@ def __init__(self, name): def add_version(self, version): self.versions.append(version) - def __str__(self): - out = "%%%s" % self.name + def stringify(self, **kwargs): + color = kwargs.get("color", False) + + out = StringIO() + out.write("%s{%%%s}" % (compiler_fmt, self.name)) + if self.versions: vlist = ",".join(str(v) for v in sorted(self.versions)) - out += "@%s" % vlist - return out + out.write("%s{@%s}" % (compiler_fmt, vlist)) + return out.getvalue() + + def __str__(self): + return self.stringify() class Spec(object): def __init__(self, name): self.name = name + self._package = None self.versions = [] self.variants = {} + self.architecture = None self.compiler = None self.dependencies = {} @@ -124,37 +151,76 @@ def add_compiler(self, compiler): "Spec for '%s' cannot have two compilers." % self.name) self.compiler = compiler + def add_architecture(self, architecture): + if self.architecture: raise DuplicateArchitectureError( + "Spec for '%s' cannot have two architectures." % self.name) + self.architecture = architecture + def add_dependency(self, dep): if dep.name in self.dependencies: raise DuplicateDependencyError("Cannot depend on '%s' twice" % dep) self.dependencies[dep.name] = dep - def __str__(self): - out = self.name + def canonicalize(self): + """Ensures that the spec is in canonical form. + + This means: + 1. All dependencies of this package and of its dependencies are + in the dependencies list (transitive closure of deps). + 2. All dependencies in the dependencies list are canonicalized. + + This function also serves to validate the spec, in that it makes sure + that each package exists an that spec criteria don't violate package + criteria. + """ + pass + + @property + def package(self): + if self._package == None: + self._package = packages.get(self.name) + return self._package + + def stringify(self, **kwargs): + color = kwargs.get("color", False) + + out = ColorStream(StringIO(), color) + out.write("%s" % self.name) if self.versions: vlist = ",".join(str(v) for v in sorted(self.versions)) - out += "@%s" % vlist + out.write("%s{@%s}" % (version_fmt, vlist)) if self.compiler: - out += str(self.compiler) + out.write(self.compiler.stringify(color=color)) for name in sorted(self.variants.keys()): enabled = self.variants[name] if enabled: - out += '+%s' % name + out.write('%s{+%s}' % (variant_enabled_fmt, name)) else: - out += '~%s' % name + out.write('%s{~%s}' % (variant_disabled_fmt, name)) + + if self.architecture: + out.write("%s{=%s}" % (architecture_fmt, self.architecture)) for name in sorted(self.dependencies.keys()): - out += " ^%s" % str(self.dependencies[name]) + dep = " ^" + self.dependencies[name].stringify(color=color) + out.write(dep, raw=True) - return out + return out.getvalue() + + def write(self, stream=sys.stdout): + isatty = stream.isatty() + stream.write(self.stringify(color=isatty)) + + def __str__(self): + return self.stringify() # # These are possible token types in the spec grammar. # -DEP, AT, COLON, COMMA, ON, OFF, PCT, ID = range(8) +DEP, AT, COLON, COMMA, ON, OFF, PCT, EQ, ID = range(9) class SpecLexer(spack.parse.Lexer): """Parses tokens that make up spack specs.""" @@ -168,6 +234,7 @@ def __init__(self): (r'\-', lambda scanner, val: self.token(OFF, val)), (r'\~', lambda scanner, val: self.token(OFF, val)), (r'\%', lambda scanner, val: self.token(PCT, val)), + (r'\=', lambda scanner, val: self.token(EQ, val)), (r'\w[\w.-]*', lambda scanner, val: self.token(ID, val)), (r'\s+', lambda scanner, val: None)]) @@ -206,24 +273,35 @@ def spec(self): spec.add_version(version) elif self.accept(ON): - self.expect(ID) - self.check_identifier() - spec.add_variant(self.token.value, True) + spec.add_variant(self.variant(), True) elif self.accept(OFF): - self.expect(ID) - self.check_identifier() - spec.add_variant(self.token.value, False) + spec.add_variant(self.variant(), False) elif self.accept(PCT): spec.add_compiler(self.compiler()) + elif self.accept(EQ): + spec.add_architecture(self.architecture()) + else: break return spec + def variant(self): + self.expect(ID) + self.check_identifier() + return self.token.value + + + def architecture(self): + self.expect(ID) + self.check_identifier() + return self.token.value + + def version(self): start = None end = None diff --git a/lib/spack/spack/tty.py b/lib/spack/spack/tty.py index dc31c5456c..531f5660f7 100644 --- a/lib/spack/spack/tty.py +++ b/lib/spack/spack/tty.py @@ -1,61 +1,46 @@ import sys import spack +from spack.color import cprint indent = " " -def escape(s): - """Returns a TTY escape code if stdout is a tty, otherwise empty string""" - if sys.stdout.isatty(): - return "\033[{}m".format(s) - return '' +def msg(message, *args): + cprint("@*b{==>} @*w{%s}" % str(message)) + for arg in args: + print indent + str(arg) -def color(n): - return escape("0;{}".format(n)) -def bold(n): - return escape("1;{}".format(n)) +def info(message, *args, **kwargs): + format = kwargs.get('format', '*b') + cprint("@%s{==>} %s" % (format, str(message))) + for arg in args: + print indent + str(arg) -def underline(n): - return escape("4;{}".format(n)) -blue = bold(34) -white = bold(39) -red = bold(31) -yellow = underline(33) -green = bold(92) -gray = bold(30) -em = underline(39) -reset = escape(0) +def verbose(message, *args): + if spack.verbose: + info(message, *args, format='*g') -def msg(msg, *args, **kwargs): - color = kwargs.get("color", blue) - print "{}==>{} {}{}".format(color, white, str(msg), reset) - for arg in args: print indent + str(arg) - -def info(msg, *args, **kwargs): - color = kwargs.get("color", blue) - print "{}==>{} {}".format(color, reset, str(msg)) - for arg in args: print indent + str(arg) - -def verbose(msg, *args): - if spack.verbose: info(msg, *args, color=green) def debug(*args): - if spack.debug: msg(*args, color=red) + if spack.debug: + info(message, *args, format='*c') -def error(msg, *args): - print "{}Error{}: {}".format(red, reset, str(msg)) - for arg in args: print indent + str(arg) -def warn(msg, *args): - print "{}Warning{}: {}".format(yellow, reset, str(msg)) - for arg in args: print indent + str(arg) +def error(message, *args): + info(message, *args, format='*r') -def die(msg, *args): - error(msg, *args) + +def warn(message, *args): + info(message, *args, format='*Y') + + +def die(message, *args): + error(message, *args) sys.exit(1) -def pkg(msg): + +def pkg(message): """Outputs a message with a package icon.""" import platform from version import Version @@ -64,5 +49,5 @@ def pkg(msg): if mac_ver and Version(mac_ver) >= Version('10.7'): print u"\U0001F4E6" + indent, else: - print '{}[+]{} '.format(green, reset), - print msg + cprint('@*g{[+]} ') + print message