Repository: spack/spack

Issue:
Cryptic error message for compilers.yaml after OS update
I'm getting this weird error every time I try to run `spack spec` or `spack install` on the latest develop:

```
Traceback (most recent call last):
  File "/home/ajstewart/spack/bin/spack", line 179, in <module>
    main()
  File "/home/ajstewart/spack/bin/spack", line 157, in main
    return_val = command(parser, args)
  File "/home/ajstewart/spack/lib/spack/spack/cmd/spec.py", line 58, in spec
    spec.concretize()
  File "/home/ajstewart/spack/lib/spack/spack/spec.py", line 1246, in concretize
    self._concretize_helper())
  File "/home/ajstewart/spack/lib/spack/spack/spec.py", line 1067, in _concretize_helper
    name].spec._concretize_helper(presets, visited)
  File "/home/ajstewart/spack/lib/spack/spack/spec.py", line 1067, in _concretize_helper
    name].spec._concretize_helper(presets, visited)
  File "/home/ajstewart/spack/lib/spack/spack/spec.py", line 1078, in _concretize_helper
    spack.concretizer.concretize_compiler(self),
  File "/home/ajstewart/spack/lib/spack/spack/concretize.py", line 345, in concretize_compiler
    while not _proper_compiler_style(matches[index], spec.architecture):
IndexError: list index out of range
```

Oddly enough, I don't see this error message on our cluster; only on my laptop. I updated my laptop to Fedora 24 the other day, so that may be related. It now runs Python 2.7.12. Does anyone have any ideas?



Discussion:
Figured it out. The problem was that my `compilers.yaml` still had the Fedora 23 compiler. Just had to re-run `spack compiler add` and it works now.

We should probably add a more useful error message for having no available compilers on this OS.

Got hit with this again after some friendly sys-admin upgraded a machine I haven't used in a few weeks. I, of course, completely forgot I had gotten this problem before #1851 and only a search of issues refreshed my memory.  You definitely need spack to be a bit nicer with useful error messages in this situation. I am already missing too much hair :-)
I will look at this by the end of the week and provide an update by the end of 3/10

Resolving patch:
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
@@ -229,10 +229,15 @@ def compilers_for_spec(compiler_spec, arch_spec=None, scope=None,
     matches = set(find(compiler_spec, scope))
     compilers = []
     for cspec in matches:
-        compilers.extend(get_compilers(cspec, config, arch_spec))
+        compilers.extend(get_compilers(config, cspec, arch_spec))
     return compilers
 
 
+def compilers_for_arch(arch_spec, scope=None):
+    config = all_compilers_config(scope)
+    return list(get_compilers(config, arch_spec=arch_spec))
+
+
 def compiler_from_config_entry(items):
     cspec = spack.spec.CompilerSpec(items['spec'])
     os = items.get('operating_system', None)
@@ -265,12 +270,12 @@ def compiler_from_config_entry(items):
                environment, extra_rpaths, **compiler_flags)
 
 
-def get_compilers(cspec, config, arch_spec=None):
+def get_compilers(config, cspec=None, arch_spec=None):
     compilers = []
 
     for items in config:
         items = items['compiler']
-        if items['spec'] != str(cspec):
+        if cspec and items['spec'] != str(cspec):
             continue
 
         # If an arch spec is given, confirm that this compiler
diff --git a/lib/spack/spack/concretize.py b/lib/spack/spack/concretize.py
--- a/lib/spack/spack/concretize.py
+++ b/lib/spack/spack/concretize.py
@@ -315,11 +315,16 @@ def concretize_compiler(self, spec):
         def _proper_compiler_style(cspec, aspec):
             return spack.compilers.compilers_for_spec(cspec, arch_spec=aspec)
 
-        all_compilers = spack.compilers.all_compiler_specs()
+        all_compiler_specs = spack.compilers.all_compiler_specs()
+        if not all_compiler_specs:
+            raise spack.compilers.NoCompilersError()
 
         if (spec.compiler and
             spec.compiler.concrete and
-                spec.compiler in all_compilers):
+                spec.compiler in all_compiler_specs):
+            if not _proper_compiler_style(spec.compiler, spec.architecture):
+                _compiler_concretization_failure(
+                    spec.compiler, spec.architecture)
             return False
 
         # Find the another spec that has a compiler, or the root if none do
@@ -332,22 +337,23 @@ def _proper_compiler_style(cspec, aspec):
         assert(other_spec)
 
         # Check if the compiler is already fully specified
-        if other_compiler in all_compilers:
+        if other_compiler in all_compiler_specs:
             spec.compiler = other_compiler.copy()
+            if not _proper_compiler_style(spec.compiler, spec.architecture):
+                _compiler_concretization_failure(
+                    spec.compiler, spec.architecture)
             return True
 
         # Filter the compilers into a sorted list based on the compiler_order
         # from spackconfig
-        compiler_list = all_compilers if not other_compiler else \
+        compiler_list = all_compiler_specs if not other_compiler else \
             spack.compilers.find(other_compiler)
+        if not compiler_list:
+            # No compiler with a satisfactory spec was found
+            raise UnavailableCompilerVersionError(other_compiler)
         cmp_compilers = partial(
             pkgsort().compiler_compare, other_spec.name)
         matches = sorted(compiler_list, cmp=cmp_compilers)
-        if not matches:
-            arch = spec.architecture
-            raise UnavailableCompilerVersionError(other_compiler,
-                                                  arch.platform_os,
-                                                  arch.target)
 
         # copy concrete version into other_compiler
         try:
@@ -355,10 +361,9 @@ def _proper_compiler_style(cspec, aspec):
                 c for c in matches
                 if _proper_compiler_style(c, spec.architecture)).copy()
         except StopIteration:
-            raise UnavailableCompilerVersionError(
-                spec.compiler, spec.architecture.platform_os,
-                spec.architecture.target
-            )
+            # No compiler with a satisfactory spec has a suitable arch
+            _compiler_concretization_failure(
+                other_compiler, spec.architecture)
 
         assert(spec.compiler.concrete)
         return True  # things changed.
@@ -466,17 +471,53 @@ def find_spec(spec, condition):
     return None   # Nothing matched the condition.
 
 
+def _compiler_concretization_failure(compiler_spec, arch):
+    # Distinguish between the case that there are compilers for
+    # the arch but not with the given compiler spec and the case that
+    # there are no compilers for the arch at all
+    if not spack.compilers.compilers_for_arch(arch):
+        available_os_targets = set(
+            (c.operating_system, c.target) for c in
+            spack.compilers.all_compilers())
+        raise NoCompilersForArchError(arch, available_os_targets)
+    else:
+        raise UnavailableCompilerVersionError(compiler_spec, arch)
+
+
+class NoCompilersForArchError(spack.error.SpackError):
+    def __init__(self, arch, available_os_targets):
+        err_msg = ("No compilers found"
+                   " for operating system %s and target %s." 
+                   "\nIf previous installations have succeeded, the"
+                   " operating system may have been updated." %
+                   (arch.platform_os, arch.target))
+
+        available_os_target_strs = list()
+        for os, t in available_os_targets:
+            os_target_str = "%s-%s" % (os, t) if t else os
+            available_os_target_strs.append(os_target_str)
+        err_msg += (
+            "\nCompilers are defined for the following"
+            " operating systems and targets:\n\t" +
+            "\n\t".join(available_os_target_strs))
+
+        super(NoCompilersForArchError, self).__init__(
+            err_msg, "Run 'spack compiler find' to add compilers.")
+
+
 class UnavailableCompilerVersionError(spack.error.SpackError):
 
     """Raised when there is no available compiler that satisfies a
        compiler spec."""
 
-    def __init__(self, compiler_spec, operating_system, target):
+    def __init__(self, compiler_spec, arch=None):
+        err_msg = "No compilers with spec %s found" % compiler_spec
+        if arch:
+            err_msg += (" for operating system %s and target %s." %
+                        (compiler_spec, arch.platform_os, arch.target))
+
         super(UnavailableCompilerVersionError, self).__init__(
-            "No available compiler version matches '%s' on operating_system "
-            "'%s' for target '%s'"
-            % (compiler_spec, operating_system, target),
-            "Run 'spack compilers' to see available compiler Options.")
+            err_msg, "Run 'spack compiler find' to add compilers.")
 
 
 class NoValidVersionError(spack.error.SpackError):


Test patch:
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -211,6 +211,16 @@ def test_concretize_two_virtuals_with_dual_provider_and_a_conflict(
         with pytest.raises(spack.spec.MultipleProviderError):
             s.concretize()
 
+    def test_no_matching_compiler_specs(self):
+        s = Spec('a %gcc@0.0.0')
+        with pytest.raises(spack.concretize.UnavailableCompilerVersionError):
+            s.concretize()
+
+    def test_no_compilers_for_arch(self):
+        s = Spec('a arch=linux-rhel0-x86_64')
+        with pytest.raises(spack.concretize.NoCompilersForArchError):
+            s.concretize()
+
     def test_virtual_is_fully_expanded_for_callpath(self):
         # force dependence on fake "zmpi" by asking for MPI 10.0
         spec = Spec('callpath ^mpi@10.0')