Repository: spack/spack

Issue:
Compiler added multiple times to configuration
Using `develop` at ec2c5e5b0dfbbf5d70d0ccca91da4e8c149642fc I noticed that the `clang` compiler  I have installed in my system gets added multiple times to the configuration.


### Steps to reproduce the issue
Install `clang-6.0` from the `llvm-toolchain-6.0` repository (Ubuntu 18.04). Then from a clean checkout of Spack:
```console
$ spack compiler add
==> Added 3 new compilers to /home/mculpo/.spack/linux/compilers.yaml
    gcc@8.2.0  gcc@7.3.0  clang@6.0.1-svn334776-1~exp1~20181018152737.116
==> Compilers are defined in the following files:
    /home/mculpo/.spack/linux/compilers.yaml

$ spack compiler list
==> Available compilers
-- clang ubuntu18.04-x86_64 -------------------------------------
clang@6.0.1-svn334776-1

-- gcc ubuntu18.04-x86_64 ---------------------------------------
gcc@8.2.0  gcc@7.3.0

$ spack compiler add
==> Added 1 new compiler to /home/mculpo/.spack/linux/compilers.yaml
    clang@6.0.1-svn334776-1~exp1~20181018152737.116
==> Compilers are defined in the following files:
    /home/mculpo/.spack/linux/compilers.yaml

$ spack compiler list
==> Available compilers
-- clang ubuntu18.04-x86_64 -------------------------------------
clang@6.0.1-svn334776-1  clang@6.0.1-svn334776-1

-- gcc ubuntu18.04-x86_64 ---------------------------------------
gcc@8.2.0  gcc@7.3.0
```

### Error Message
No error messages, just every time I run:
```console
$ spack compiler add
```
`clang` gets added one more time to the configuration. The same doesn't happen for `gcc`.

### Information on your system

I am working on `Ubuntu 18.04` and the bug is triggered by the compilers in this package (which must be installed):
```console
$ dpkg -s clang-6.0
Package: clang-6.0
Status: install ok installed
Priority: optional
Section: devel
Installed-Size: 35732
Maintainer: LLVM Packaging Team <pkg-llvm-team@lists.alioth.debian.org>
Architecture: amd64
Source: llvm-toolchain-6.0
Version: 1:6.0.1~svn334776-1~exp1~20181018152737.116
Provides: c++-compiler, c-compiler, objc-compiler
Depends: libc6 (>= 2.14), libgcc1 (>= 1:3.4), libjsoncpp1 (>= 1.7.4), libllvm6.0 (>= 1:6.0~svn298832-1~), libstdc++6 (>= 5.2), libstdc++-7-dev, libgcc-7-dev, libobjc-7-dev, libclang-common-6.0-dev (= 1:6.0.1~svn334776-1~exp1~20181018152737.116), libclang1-6.0 (= 1:6.0.1~svn334776-1~exp1~20181018152737.116), libc6-dev, binutils
Recommends: llvm-6.0-dev, python, libomp-dev
Suggests: gnustep, gnustep-devel, clang-6.0-doc
Description: C, C++ and Objective-C compiler
 Clang project is a C, C++, Objective C and Objective C++ front-end
 based on the LLVM compiler. Its goal is to offer a replacement to the
 GNU Compiler Collection (GCC).
 .
 Clang implements all of the ISO C++ 1998, 11 and 14 standards and also
 provides most of the support of C++17.
Homepage: https://www.llvm.org/
```


Discussion:
The issue is due to the output of:
```console
$ clang-6.0 --version
clang version 6.0.1-svn334776-1~exp1~20181018152737.116 (branches/release_60)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
```
which makes Spack deduce the compiler's version to be:
```
6.0.1-svn334776-1~exp1~20181018152737.116
```
Whenever this version is read from `compilers.yaml` and passed to `autospec`, the parser truncates it to:
```
6.0.1-svn334776-1
```
possibly because `~` is understood to introduce a variant. **I'll submit a fix to `clang` that will result in deducing version 6.0.1 for this particular case**.

Resolving patch:
diff --git a/lib/spack/spack/compilers/clang.py b/lib/spack/spack/compilers/clang.py
--- a/lib/spack/spack/compilers/clang.py
+++ b/lib/spack/spack/compilers/clang.py
@@ -178,21 +178,32 @@ def default_version(cls, comp):
         if comp not in _version_cache:
             compiler = Executable(comp)
             output = compiler('--version', output=str, error=str)
+            _version_cache[comp] = cls.detect_version_from_str(output)
 
-            ver = 'unknown'
-            match = re.search(r'^Apple LLVM version ([^ )]+)', output)
-            if match:
-                # Apple's LLVM compiler has its own versions, so suffix them.
-                ver = match.group(1) + '-apple'
-            else:
-                # Normal clang compiler versions are left as-is
-                match = re.search(r'clang version ([^ )]+)', output)
-                if match:
-                    ver = match.group(1)
+        return _version_cache[comp]
 
-            _version_cache[comp] = ver
+    @classmethod
+    def detect_version_from_str(cls, output):
+        """Returns the version that has been detected from the string
+        passed as input. If no detection is possible returns the
+        string 'unknown'.
 
-        return _version_cache[comp]
+        Args:
+            output (str): string used to detect a compiler version
+        """
+        ver = 'unknown'
+        match = re.search(
+            # Apple's LLVM compiler has its own versions, so suffix them.
+            r'^Apple LLVM version ([^ )]+)|'
+            # Normal clang compiler versions are left as-is
+            r'clang version ([^ )]+)-svn[~.\w\d-]*|'
+            r'clang version ([^ )]+)',
+            output
+        )
+        if match:
+            suffix = '-apple' if match.lastindex == 1 else ''
+            ver = match.group(match.lastindex) + suffix
+        return ver
 
     @classmethod
     def fc_version(cls, fc):


Test patch:
diff --git a/lib/spack/spack/test/compilers.py b/lib/spack/spack/test/compilers.py
--- a/lib/spack/spack/test/compilers.py
+++ b/lib/spack/spack/test/compilers.py
@@ -3,11 +3,15 @@
 #
 # SPDX-License-Identifier: (Apache-2.0 OR MIT)
 
+import pytest
+
 from copy import copy
 from six import iteritems
 
 import spack.spec
+import spack.compiler
 import spack.compilers as compilers
+import spack.compilers.clang
 from spack.compiler import _get_versioned_tuple, Compiler
 
 
@@ -227,3 +231,23 @@ def test_xl_r_flags():
     unsupported_flag_test("cxx11_flag", "xl_r@13.0")
     supported_flag_test("cxx11_flag", "-qlanglvl=extended0x", "xl_r@13.1")
     supported_flag_test("pic_flag", "-qpic", "xl_r@1.0")
+
+
+@pytest.mark.regression('10191')
+@pytest.mark.parametrize('version_str,expected_version', [
+    # macOS clang
+    ('Apple LLVM version 7.0.2 (clang-700.1.81)\n'
+     'Target: x86_64-apple-darwin15.2.0\n'
+     'Thread model: posix\n', '7.0.2-apple'),
+    # Other platforms
+    ('clang version 6.0.1-svn334776-1~exp1~20181018152737.116 (branches/release_60)\n'  # noqa
+     'Target: x86_64-pc-linux-gnu\n'
+     'Thread model: posix\n'
+     'InstalledDir: /usr/bin\n', '6.0.1'),
+    ('clang version 3.1 (trunk 149096)\n'
+     'Target: x86_64-unknown-linux-gnu\n'
+     'Thread model: posix\n', '3.1'),
+])
+def test_clang_version_detection(version_str, expected_version):
+    version = spack.compilers.clang.Clang.detect_version_from_str(version_str)
+    assert version == expected_version