Better spack spec (#2238)
* Add options for hashes, tree depth, and YAML to `spack spec`.
- Can now display hashes with `spack spec`, like `spack find`.
  - Removed the old "ids" argument to `spack spec` (which
    printed numerical values)b
- Can output YAML spec from `spack spec` with `-y`
- Can control depth of DAG traversal with --cover=[nodes|edges|paths]
- Can print install status (installed, missing, not installed) with -I
* Don't use YAML aliases in specs.
- Force Spack's YAML dumper to ignore aliases.
- aliases cause non-canonical YAML to be used in DAG hash, and result in
  redundant hashes.
- add a test to ensure this behavior stays
			
			
This commit is contained in:
		@@ -89,3 +89,11 @@ def _specs(self, **kwargs):
 | 
			
		||||
_arguments['dirty'] = Args(
 | 
			
		||||
    '--dirty', action='store_true', dest='dirty',
 | 
			
		||||
    help='Do NOT clean environment before installing.')
 | 
			
		||||
 | 
			
		||||
_arguments['long'] = Args(
 | 
			
		||||
    '-l', '--long', action='store_true',
 | 
			
		||||
    help='Show dependency hashes as well as versions.')
 | 
			
		||||
 | 
			
		||||
_arguments['very_long'] = Args(
 | 
			
		||||
    '-L', '--very-long', action='store_true',
 | 
			
		||||
    help='Show full dependency hashes as well as versions.')
 | 
			
		||||
 
 | 
			
		||||
@@ -52,14 +52,8 @@ def setup_parser(subparser):
 | 
			
		||||
        const='deps',
 | 
			
		||||
        help='Show full dependency DAG of installed packages')
 | 
			
		||||
 | 
			
		||||
    subparser.add_argument('-l', '--long',
 | 
			
		||||
                           action='store_true',
 | 
			
		||||
                           dest='long',
 | 
			
		||||
                           help='Show dependency hashes as well as versions.')
 | 
			
		||||
    subparser.add_argument('-L', '--very-long',
 | 
			
		||||
                           action='store_true',
 | 
			
		||||
                           dest='very_long',
 | 
			
		||||
                           help='Show dependency hashes as well as versions.')
 | 
			
		||||
    arguments.add_common_arguments(subparser, ['long', 'very_long'])
 | 
			
		||||
 | 
			
		||||
    subparser.add_argument('-f', '--show-flags',
 | 
			
		||||
                           action='store_true',
 | 
			
		||||
                           dest='show_flags',
 | 
			
		||||
 
 | 
			
		||||
@@ -23,36 +23,57 @@
 | 
			
		||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 | 
			
		||||
##############################################################################
 | 
			
		||||
import argparse
 | 
			
		||||
import spack.cmd
 | 
			
		||||
 | 
			
		||||
import spack
 | 
			
		||||
import spack.cmd
 | 
			
		||||
import spack.cmd.common.arguments as arguments
 | 
			
		||||
 | 
			
		||||
description = "print out abstract and concrete versions of a spec."
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def setup_parser(subparser):
 | 
			
		||||
    subparser.add_argument('-i', '--ids', action='store_true',
 | 
			
		||||
                           help="show numerical ids for dependencies.")
 | 
			
		||||
    arguments.add_common_arguments(subparser, ['long', 'very_long'])
 | 
			
		||||
    subparser.add_argument(
 | 
			
		||||
        '-y', '--yaml', action='store_true', default=False,
 | 
			
		||||
        help='Print concrete spec as YAML.')
 | 
			
		||||
    subparser.add_argument(
 | 
			
		||||
        '-c', '--cover', action='store',
 | 
			
		||||
        default='nodes', choices=['nodes', 'edges', 'paths'],
 | 
			
		||||
        help='How extensively to traverse the DAG. (default: nodes).')
 | 
			
		||||
    subparser.add_argument(
 | 
			
		||||
        '-I', '--install-status', action='store_true', default=False,
 | 
			
		||||
        help='Show install status of packages.  Packages can be: '
 | 
			
		||||
             'installed [+], missing and needed by an installed package [-], '
 | 
			
		||||
             'or not installed (no annotation).')
 | 
			
		||||
    subparser.add_argument(
 | 
			
		||||
        'specs', nargs=argparse.REMAINDER, help="specs of packages")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def spec(parser, args):
 | 
			
		||||
    kwargs = {'ids': args.ids,
 | 
			
		||||
              'indent': 2,
 | 
			
		||||
              'color': True}
 | 
			
		||||
    kwargs = {'color': True,
 | 
			
		||||
              'cover': args.cover,
 | 
			
		||||
              'install_status': args.install_status,
 | 
			
		||||
              'hashes': args.long or args.very_long,
 | 
			
		||||
              'hashlen': None if args.very_long else 7}
 | 
			
		||||
 | 
			
		||||
    for spec in spack.cmd.parse_specs(args.specs):
 | 
			
		||||
        # With -y, just print YAML to output.
 | 
			
		||||
        if args.yaml:
 | 
			
		||||
            spec.concretize()
 | 
			
		||||
            print spec.to_yaml()
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        # Print some diagnostic info by default.
 | 
			
		||||
        print "Input spec"
 | 
			
		||||
        print "------------------------------"
 | 
			
		||||
        print "--------------------------------"
 | 
			
		||||
        print spec.tree(**kwargs)
 | 
			
		||||
 | 
			
		||||
        print "Normalized"
 | 
			
		||||
        print "------------------------------"
 | 
			
		||||
        print "--------------------------------"
 | 
			
		||||
        spec.normalize()
 | 
			
		||||
        print spec.tree(**kwargs)
 | 
			
		||||
 | 
			
		||||
        print "Concretized"
 | 
			
		||||
        print "------------------------------"
 | 
			
		||||
        print "--------------------------------"
 | 
			
		||||
        spec.concretize()
 | 
			
		||||
        print spec.tree(**kwargs)
 | 
			
		||||
 
 | 
			
		||||
@@ -2396,12 +2396,24 @@ def __cmp__(self, other):
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.format() + self.dep_string()
 | 
			
		||||
 | 
			
		||||
    def _install_status(self):
 | 
			
		||||
        """Helper for tree to print DB install status."""
 | 
			
		||||
        if not self.concrete:
 | 
			
		||||
            return None
 | 
			
		||||
        try:
 | 
			
		||||
            record = spack.store.db.get_record(self)
 | 
			
		||||
            return record.installed
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def tree(self, **kwargs):
 | 
			
		||||
        """Prints out this spec and its dependencies, tree-formatted
 | 
			
		||||
           with indentation."""
 | 
			
		||||
        color = kwargs.pop('color', False)
 | 
			
		||||
        depth = kwargs.pop('depth', False)
 | 
			
		||||
        showid = kwargs.pop('ids',   False)
 | 
			
		||||
        hashes = kwargs.pop('hashes', True)
 | 
			
		||||
        hlen = kwargs.pop('hashlen', None)
 | 
			
		||||
        install_status = kwargs.pop('install_status', True)
 | 
			
		||||
        cover = kwargs.pop('cover', 'nodes')
 | 
			
		||||
        indent = kwargs.pop('indent', 0)
 | 
			
		||||
        fmt = kwargs.pop('format', '$_$@$%@+$+$=')
 | 
			
		||||
@@ -2410,8 +2422,6 @@ def tree(self, **kwargs):
 | 
			
		||||
        check_kwargs(kwargs, self.tree)
 | 
			
		||||
 | 
			
		||||
        out = ""
 | 
			
		||||
        cur_id = 0
 | 
			
		||||
        ids = {}
 | 
			
		||||
        for d, node in self.traverse(
 | 
			
		||||
                order='pre', cover=cover, depth=True, deptypes=deptypes):
 | 
			
		||||
            if prefix is not None:
 | 
			
		||||
@@ -2419,11 +2429,17 @@ def tree(self, **kwargs):
 | 
			
		||||
            out += " " * indent
 | 
			
		||||
            if depth:
 | 
			
		||||
                out += "%-4d" % d
 | 
			
		||||
            if not id(node) in ids:
 | 
			
		||||
                cur_id += 1
 | 
			
		||||
                ids[id(node)] = cur_id
 | 
			
		||||
            if showid:
 | 
			
		||||
                out += "%-4d" % ids[id(node)]
 | 
			
		||||
            if install_status:
 | 
			
		||||
                status = node._install_status()
 | 
			
		||||
                if status is None:
 | 
			
		||||
                    out += "     "  # Package isn't installed
 | 
			
		||||
                elif status:
 | 
			
		||||
                    out += colorize("@g{[+]}  ", color=color)  # installed
 | 
			
		||||
                else:
 | 
			
		||||
                    out += colorize("@r{[-]}  ", color=color)  # missing
 | 
			
		||||
 | 
			
		||||
            if hashes:
 | 
			
		||||
                out += colorize('@K{%s}  ', color=color) % node.dag_hash(hlen)
 | 
			
		||||
            out += ("    " * d)
 | 
			
		||||
            if d > 0:
 | 
			
		||||
                out += "^"
 | 
			
		||||
 
 | 
			
		||||
@@ -90,3 +90,19 @@ def check(obj, start_line, end_line):
 | 
			
		||||
        check(self.data['config_file']['some_list'][2],   8,  8)
 | 
			
		||||
        check(self.data['config_file']['another_list'],  10, 10)
 | 
			
		||||
        check(self.data['config_file']['some_key'],      11, 11)
 | 
			
		||||
 | 
			
		||||
    def test_yaml_aliases(self):
 | 
			
		||||
        aliased_list_1 = ['foo']
 | 
			
		||||
        aliased_list_2 = []
 | 
			
		||||
        dict_with_aliases = {
 | 
			
		||||
            'a': aliased_list_1,
 | 
			
		||||
            'b': aliased_list_1,
 | 
			
		||||
            'c': aliased_list_1,
 | 
			
		||||
            'd': aliased_list_2,
 | 
			
		||||
            'e': aliased_list_2,
 | 
			
		||||
            'f': aliased_list_2,
 | 
			
		||||
        }
 | 
			
		||||
        string = syaml.dump(dict_with_aliases)
 | 
			
		||||
 | 
			
		||||
        # ensure no YAML aliases appear in syaml dumps.
 | 
			
		||||
        self.assertFalse('*id' in string)
 | 
			
		||||
 
 | 
			
		||||
@@ -202,6 +202,11 @@ def represent_mapping(self, tag, mapping, flow_style=None):
 | 
			
		||||
                node.flow_style = best_style
 | 
			
		||||
        return node
 | 
			
		||||
 | 
			
		||||
    def ignore_aliases(self, _data):
 | 
			
		||||
        """Make the dumper NEVER print YAML aliases."""
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Make our special objects look like normal YAML ones.
 | 
			
		||||
OrderedLineDumper.add_representer(syaml_dict, OrderedLineDumper.represent_dict)
 | 
			
		||||
OrderedLineDumper.add_representer(syaml_list, OrderedLineDumper.represent_list)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user