Repository: spack/spack

Issue:
`spack compiler find` broken, `spack compiler add` no longer accepts paths
Just merged to the latest develop.

```
$ spack compiler find
Traceback (most recent call last):
  File "/soft/spack/bin/spack", line 179, in <module>
    main()
  File "/soft/spack/bin/spack", line 157, in main
    return_val = command(parser, args)
  File "/blues/gpfs/home/software/spack/lib/spack/spack/cmd/compiler.py", line 145, in compiler
    action[args.compiler_command](args)
KeyError: 'find'
$ spack compiler add
==> Found no new compilers
$ spack compiler add /soft/spack/opt/spack/linux-x86_64/gcc-5.3.0/gcc-6.1.0-q2zosj3igepi3pjnqt74bwazmptr5gpj
Traceback (most recent call last):
  File "/soft/spack/bin/spack", line 179, in <module>
    main()
  File "/soft/spack/bin/spack", line 157, in main
    return_val = command(parser, args)
  File "/blues/gpfs/home/software/spack/lib/spack/spack/cmd/compiler.py", line 145, in compiler
    action[args.compiler_command](args)
  File "/blues/gpfs/home/software/spack/lib/spack/spack/cmd/compiler.py", line 79, in compiler_add
    compilers = [c for c in spack.compilers.find_compilers(*args.add_paths)
TypeError: find_compilers() takes no arguments (1 given)
```

It seems that `spack compiler find` is no longer a valid alias? And `spack compiler add` no longer accepts paths?



Discussion:
It's also no longer picking up gfortran on CentOS 6???

``` yaml
compilers:
- compiler:
    modules: []
    operating_system: centos6
    paths:
      cc: /usr/bin/gcc
      cxx: /usr/bin/g++
      f77: null
      fc: null
    spec: gcc@4.4.7
```

Just looked at lib/spack/spack/cmd/compiler.py and the alias was indeed removed. Looks like a bad merge.


Resolving patch:
diff --git a/lib/spack/spack/cmd/compiler.py b/lib/spack/spack/cmd/compiler.py
--- a/lib/spack/spack/cmd/compiler.py
+++ b/lib/spack/spack/cmd/compiler.py
@@ -69,7 +69,7 @@ def setup_parser(subparser):
                              help="Configuration scope to read from.")
 
 
-def compiler_add(args):
+def compiler_find(args):
     """Search either $PATH or a list of paths OR MODULES for compilers and add them
        to Spack's configuration."""
     paths = args.add_paths
@@ -78,7 +78,6 @@ def compiler_add(args):
 
     compilers = [c for c in spack.compilers.find_compilers(*args.add_paths)
                  if c.spec not in spack.compilers.all_compilers(scope=args.scope)]
-
     if compilers:
         spack.compilers.add_compilers_to_config(compilers, scope=args.scope)
         n = len(compilers)
@@ -93,7 +92,6 @@ def compiler_add(args):
 def compiler_remove(args):
     cspec = CompilerSpec(args.compiler_spec)
     compilers = spack.compilers.compilers_for_spec(cspec, scope=args.scope)
-
     if not compilers:
         tty.die("No compilers match spec %s" % cspec)
     elif not args.all and len(compilers) > 1:
@@ -137,9 +135,10 @@ def compiler_list(args):
 
 
 def compiler(parser, args):
-    action = { 'add'    : compiler_add,
-               'remove' : compiler_remove,
-               'rm'     : compiler_remove,
-               'info'   : compiler_info,
-               'list'   : compiler_list }
+    action = {'add'    : compiler_find, 
+              'find'   : compiler_find,
+              'remove' : compiler_remove,
+              'rm'     : compiler_remove,
+              'info'   : compiler_info,
+              'list'   : compiler_list }
     action[args.compiler_command](args)
diff --git a/lib/spack/spack/compilers/__init__.py b/lib/spack/spack/compilers/__init__.py
--- a/lib/spack/spack/compilers/__init__.py
+++ b/lib/spack/spack/compilers/__init__.py
@@ -52,6 +52,7 @@
 _imported_compilers_module = 'spack.compilers'
 _path_instance_vars = ['cc', 'cxx', 'f77', 'fc']
 _other_instance_vars = ['modules', 'operating_system']
+_cache_config_file = []
 
 # TODO: customize order in config file
 if platform.system() == 'Darwin':
@@ -79,9 +80,7 @@ def _to_dict(compiler):
     if compiler.alias:
         d['alias'] = compiler.alias
 
-    return {
-            'compiler': d
-    }
+    return {'compiler': d}
 
 
 def get_compiler_config(scope=None):
@@ -99,7 +98,6 @@ def init_compiler_config():
     # Update the configuration if there are currently no compilers
     # configured.  Avoid updating automatically if there ARE site
     # compilers configured but no user ones.
-#    if (isinstance(arch, basestring) or arch == my_arch) and arch not in config:
     if not config:
         if scope is None:
             # We know no compilers were configured in any scope.
@@ -112,8 +110,11 @@ def init_compiler_config():
             if not site_config:
                 init_compiler_config()
                 config = spack.config.get_config('compilers', scope=scope)
-
-    return config
+        return config
+    elif config:
+        return config
+    else:
+        return []  # Return empty list which we will later append to.
 
 
 def add_compilers_to_config(compilers, scope=None):
@@ -126,7 +127,8 @@ def add_compilers_to_config(compilers, scope=None):
     compiler_config = get_compiler_config(scope)
     for compiler in compilers:
         compiler_config.append(_to_dict(compiler))
-
+    global _cache_config_file
+    _cache_config_file = compiler_config
     spack.config.update_config('compilers', compiler_config, scope)
 
 
@@ -139,15 +141,17 @@ def remove_compiler_from_config(compiler_spec, scope=None):
       - scope:          configuration scope to modify.
     """
     compiler_config = get_compiler_config(scope)
-    matches = [(a,c) for (a,c) in compiler_config.items() if c['spec'] == compiler_spec]
-    if len(matches) == 1:
-        del compiler_config[matches[0][0]]
-    else:
+    config_length = len(compiler_config)
+    
+    filtered_compiler_config = [comp for comp in compiler_config 
+               if spack.spec.CompilerSpec(comp['compiler']['spec']) != compiler_spec]
+    # Need a better way for this
+    global _cache_config_file
+    _cache_config_file = filtered_compiler_config # Update the cache for changes
+    if len(filtered_compiler_config) == config_length: # No items removed
         CompilerSpecInsufficientlySpecificError(compiler_spec)
+    spack.config.update_config('compilers', filtered_compiler_config, scope)
 
-    spack.config.update_config('compilers', compiler_config, scope)
-
-_cache_config_file = {}
 
 def all_compilers_config(scope=None):
     """Return a set of specs for all the compiler versions currently
@@ -155,14 +159,13 @@ def all_compilers_config(scope=None):
     """
     # Get compilers for this architecture.
     global _cache_config_file #Create a cache of the config file so we don't load all the time.
-
     if not _cache_config_file:
         _cache_config_file = get_compiler_config(scope)
         return _cache_config_file
-
     else:
         return _cache_config_file
 
+
 def all_compilers(scope=None):
     # Return compiler specs from the merged config.
     return [spack.spec.CompilerSpec(s['compiler']['spec'])
@@ -181,7 +184,7 @@ def default_compiler():
     return sorted(versions)[-1]
 
 
-def find_compilers():
+def find_compilers(*paths):
     """Return a list of compilers found in the suppied paths.
        This invokes the find_compilers() method for each operating
        system associated with the host platform, and appends
@@ -190,11 +193,11 @@ def find_compilers():
     # Find compilers for each operating system class
     oss = all_os_classes()
     compiler_lists = []
-    for os in oss:
-        compiler_lists.extend(os.find_compilers())
-
+    for o in oss:
+        compiler_lists.extend(o.find_compilers(*paths))
     return compiler_lists
 
+
 def supported_compilers():
     """Return a set of names of compilers supported by Spack.
 


Test patch:
diff --git a/lib/spack/spack/test/__init__.py b/lib/spack/spack/test/__init__.py
--- a/lib/spack/spack/test/__init__.py
+++ b/lib/spack/spack/test/__init__.py
@@ -40,7 +40,7 @@
               'cc', 'link_tree', 'spec_yaml', 'optional_deps',
               'make_executable', 'configure_guess', 'lock', 'database',
               'namespace_trie', 'yaml', 'sbang', 'environment', 'cmd.find',
-              'cmd.uninstall', 'cmd.test_install']
+              'cmd.uninstall', 'cmd.test_install', 'cmd.test_compiler_cmd']
 
 
 def list_tests():
diff --git a/lib/spack/spack/test/cmd/test_compiler_cmd.py b/lib/spack/spack/test/cmd/test_compiler_cmd.py
new file mode 100644
--- /dev/null
+++ b/lib/spack/spack/test/cmd/test_compiler_cmd.py
@@ -0,0 +1,34 @@
+import spack.spec
+import spack.cmd.compiler
+import spack.compilers
+from spack.test.mock_packages_test import *
+
+
+class MockArgs(object):
+    def __init__(self, add_paths=[], scope=None, compiler_spec=None, all=None):
+        self.add_paths = add_paths
+        self.scope = scope
+        self.compiler_spec = compiler_spec
+        self.all = all
+
+
+class CompilerCmdTest(MockPackagesTest):
+    """ Test compiler commands for add and remove """
+
+    def test_compiler_remove(self):
+        args = MockArgs(all=True, compiler_spec='gcc@4.5.0')
+        spack.cmd.compiler.compiler_remove(args)
+        compilers = spack.compilers.all_compilers()
+        self.assertTrue(spack.spec.CompilerSpec("gcc@4.5.0") not in compilers)
+
+    def test_compiler_add(self):
+        # Probably not a good a assumption but might try finding local
+        # compilers
+        # installed in /usr
+        compilers = spack.compilers.all_compilers()
+        s = set(compilers)
+        args = MockArgs(add_paths=["/usr"])
+        spack.cmd.compiler.compiler_find(args)
+        new_compilers = spack.compilers.all_compilers()
+        new_compiler = [x for x in new_compilers if x not in s]
+        self.assertTrue(new_compiler)