Repository: spack/spack

Issue:
spack find : make it accept anonymous specs for v0.10 ?
Many commands already accepts anonymous specs that are used to query the db for a constraint based selection. What is the general sentiment on extending this to `spack find`, i.e. allowing for something like:

```
# finds all the installed packages that depends on openmpi
spack find ^openmpi 

# finds all the packages that are compiled with intel
spack find %intel 
```

?



Discussion:
1. Do we have a decent algorithm for this stuff?
2. Both of the above tasks could be accomplished by grepping through the spec files that get written into package installations.

> Do we have a decent algorithm for this stuff?

We definitely do, as `spack uninstall` already works this way. Honestly, I'm starting to get confused/worried about this because it seems like every command has its own spec algorithm:
1. `spack uninstall` works without a package but `spack find` doesn't.
2. `spack compiler` and `spack install` can handle non-specific compiler versions like `gcc@4` but specs can't handle dependencies on `python@2`.

Honestly, I don't know anything about the backend for this, but is there any way we can merge these algorithms, if just for the sake of consistency? Or are these command-specific differences necessary?

I agree on merging algos.  Also, `spack module find` and `spack view` has
similar but different algos.

On Sun, Oct 30, 2016 at 9:46 AM, Adam J. Stewart notifications@github.com
wrote:

> Do we have a decent algorithm for this stuff?
> 
> We definitely do, as spack uninstall already works this way. Honestly,
> I'm starting to get confused/worried about this because it seems like every
> command has its own spec algorithm:
> 1. spack uninstall works without a package but spack find doesn't.
> 2. spack compiler and spack install can handle non-specific compiler
>    versions like gcc@4 but specs can't handle dependencies on python@2.
> 
> Honestly, I don't know anything about the backend for this, but is there
> any way we can merge these algorithms, if just for the sake of consistency?
> Or are these command-specific differences necessary?
> 
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> https://github.com/LLNL/spack/issues/2170#issuecomment-257151865, or mute
> the thread
> https://github.com/notifications/unsubscribe-auth/AB1cd9A5HhyshkDJ4wVE5uxCLWi04bIkks5q5J-ZgaJpZM4KkIeO
> .

I started a while ago collecting common arguments / algorithms that are used in commands. The file in which they are gathered is `lib/spack/spack/cmd/common/arguments.py`. The algorithm to do this search is in `ConstraintAction`.


Resolving patch:
diff --git a/lib/spack/spack/cmd/common/arguments.py b/lib/spack/spack/cmd/common/arguments.py
--- a/lib/spack/spack/cmd/common/arguments.py
+++ b/lib/spack/spack/cmd/common/arguments.py
@@ -46,19 +46,25 @@ class ConstraintAction(argparse.Action):
     """Constructs a list of specs based on a constraint given on the command line
 
     An instance of this class is supposed to be used as an argument action
-    in a parser. It will read a constraint and will attach a list of matching
-    specs to the namespace
+    in a parser. It will read a constraint and will attach a function to the
+    arguments that accepts optional keyword arguments.
+
+    To obtain the specs from a command the function must be called.
     """
-    qualifiers = {}
 
     def __call__(self, parser, namespace, values, option_string=None):
         # Query specs from command line
-        d = self.qualifiers.get(namespace.subparser_name, {})
-        specs = [s for s in spack.store.db.query(**d)]
-        values = ' '.join(values)
+        self.values = values
+        namespace.contraint = values
+        namespace.specs = self._specs
+
+    def _specs(self, **kwargs):
+        specs = [s for s in spack.store.db.query(**kwargs)]
+        values = ' '.join(self.values)
         if values:
             specs = [x for x in specs if x.satisfies(values, strict=True)]
-        namespace.specs = specs
+        return specs
+
 
 _arguments['constraint'] = Args(
     'constraint', nargs='*', action=ConstraintAction,
diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py
--- a/lib/spack/spack/cmd/find.py
+++ b/lib/spack/spack/cmd/find.py
@@ -22,16 +22,11 @@
 # License along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import argparse
 import sys
 
 import llnl.util.tty as tty
-import spack
-import spack.spec
-import spack.store
-from llnl.util.lang import *
-from llnl.util.tty.colify import *
-from llnl.util.tty.color import *
+import spack.cmd.common.arguments as arguments
+
 from spack.cmd import display_specs
 
 description = "Find installed spack packages"
@@ -43,6 +38,7 @@ def setup_parser(subparser):
                               action='store_const',
                               dest='mode',
                               const='short',
+                              default='short',
                               help='Show only specs (default)')
     format_group.add_argument('-p', '--paths',
                               action='store_const',
@@ -68,12 +64,12 @@ def setup_parser(subparser):
                            action='store_true',
                            dest='show_flags',
                            help='Show spec compiler flags.')
-
-    subparser.add_argument(
+    implicit_explicit = subparser.add_mutually_exclusive_group()
+    implicit_explicit.add_argument(
         '-e', '--explicit',
         action='store_true',
         help='Show only specs that were installed explicitly')
-    subparser.add_argument(
+    implicit_explicit.add_argument(
         '-E', '--implicit',
         action='store_true',
         help='Show only specs that were installed as dependencies')
@@ -100,17 +96,10 @@ def setup_parser(subparser):
                            action='store_true',
                            help='Show fully qualified package names.')
 
-    subparser.add_argument('query_specs',
-                           nargs=argparse.REMAINDER,
-                           help='optional specs to filter results')
+    arguments.add_common_arguments(subparser, ['constraint'])
 
 
 def query_arguments(args):
-    # Check arguments
-    if args.explicit and args.implicit:
-        tty.error('You can\'t pass -E and -e options simultaneously.')
-        raise SystemExit(1)
-
     # Set up query arguments.
     installed, known = True, any
     if args.only_missing:
@@ -129,35 +118,17 @@ def query_arguments(args):
 
 
 def find(parser, args):
-    # Filter out specs that don't exist.
-    query_specs = spack.cmd.parse_specs(args.query_specs)
-    query_specs, nonexisting = partition_list(
-        query_specs, lambda s: spack.repo.exists(s.name) or not s.name)
-
-    if nonexisting:
-        msg = "No such package%s: " % ('s' if len(nonexisting) > 1 else '')
-        msg += ", ".join(s.name for s in nonexisting)
-        tty.msg(msg)
-
-        if not query_specs:
-            return
-
     q_args = query_arguments(args)
-
-    # Get all the specs the user asked for
+    query_specs = args.specs(**q_args)
+    # Exit early if no package matches the constraint
     if not query_specs:
-        specs = set(spack.store.db.query(**q_args))
-    else:
-        results = [set(spack.store.db.query(qs, **q_args))
-                   for qs in query_specs]
-        specs = set.union(*results)
-
-    if not args.mode:
-        args.mode = 'short'
-
+        msg = "No package matches the query: {0}".format(args.contraint)
+        tty.msg(msg)
+        return
+    # Display the result
     if sys.stdout.isatty():
-        tty.msg("%d installed packages." % len(specs))
-    display_specs(specs,
+        tty.msg("%d installed packages." % len(query_specs))
+    display_specs(query_specs,
                   mode=args.mode,
                   long=args.long,
                   very_long=args.very_long,
diff --git a/lib/spack/spack/cmd/module.py b/lib/spack/spack/cmd/module.py
--- a/lib/spack/spack/cmd/module.py
+++ b/lib/spack/spack/cmd/module.py
@@ -244,17 +244,17 @@ def module(parser, args):
             'known': True
         },
     }
-    arguments.ConstraintAction.qualifiers.update(constraint_qualifiers)
-
+    query_args = constraint_qualifiers.get(args.subparser_name, {})
+    specs = args.specs(**query_args)
     module_type = args.module_type
     constraint = args.constraint
     try:
-        callbacks[args.subparser_name](module_type, args.specs, args)
+        callbacks[args.subparser_name](module_type, specs, args)
     except MultipleMatches:
         message = ('the constraint \'{query}\' matches multiple packages, '
                    'and this is not allowed in this context')
         tty.error(message.format(query=constraint))
-        for s in args.specs:
+        for s in specs:
             sys.stderr.write(s.format(color=True) + '\n')
         raise SystemExit(1)
     except NoMatch:


Test patch:
diff --git a/lib/spack/spack/test/cmd/find.py b/lib/spack/spack/test/cmd/find.py
--- a/lib/spack/spack/test/cmd/find.py
+++ b/lib/spack/spack/test/cmd/find.py
@@ -52,5 +52,3 @@ def test_query_arguments(self):
         args.implicit = True
         q_args = query_arguments(args)
         self.assertEqual(q_args['explicit'], False)
-        args.explicit = True
-        self.assertRaises(SystemExit, query_arguments, args)
diff --git a/lib/spack/spack/test/cmd/module.py b/lib/spack/spack/test/cmd/module.py
--- a/lib/spack/spack/test/cmd/module.py
+++ b/lib/spack/spack/test/cmd/module.py
@@ -34,7 +34,7 @@ class TestModule(spack.test.mock_database.MockDatabase):
 
     def _get_module_files(self, args):
         return [modules.module_types[args.module_type](spec).file_name
-                for spec in args.specs]
+                for spec in args.specs()]
 
     def test_module_common_operations(self):
         parser = argparse.ArgumentParser()