Repository: spack/spack

Issue:
Want to be able to print specific message for conflicts()
I know we've mentioned this elsewhere, but I don't think anyone opened up a dedicated issue for it. Let's say I have a package (`qt-creator`) that requires a C++14 compiler. Currently, I can add the following directive:
```python
conflicts('%gcc@:4.8', when='@4.3.0:')
```
Now, when I try to concretize qt-creator with an older GCC compiler, I get:
```
==> Error: Conflicts in concretized spec "qt-creator@4.3.1%gcc@4.4.7 arch=linux-centos6-x86_64 /jljcmpi"

List of matching conflicts:

1. "%gcc@:4.8" conflicts with "qt-creator@4.3.0:" in spec "qt-creator@4.3.1%gcc@4.4.7 arch=linux-centos6-x86_64 ^bzip2@1.0.6%gcc@4.4.7+shared arch=linux-centos6-x86_64 ^font-util@1.3.1%gcc@4.4.7 arch=linux-centos6-x86_64 ^fontconfig@2.12.3%gcc@4.4.7 arch=linux-centos6-x86_64 ^freetype@2.7.1%gcc@4.4.7 arch=linux-centos6-x86_64 ^gettext@0.19.8.1%gcc@4.4.7+bzip2+curses+git~libunistring+libxml2+tar+xz arch=linux-centos6-x86_64 ^glib@2.53.1%gcc@4.4.7~libmount arch=linux-centos6-x86_64 ^gperf@3.0.4%gcc@4.4.7 arch=linux-centos6-x86_64 ^icu4c@58.2%gcc@4.4.7 arch=linux-centos6-x86_64 ^inputproto@2.3.2%gcc@4.4.7 arch=linux-centos6-x86_64 ^jpeg@9b%gcc@4.4.7 arch=linux-centos6-x86_64 ^kbproto@1.0.7%gcc@4.4.7 arch=linux-centos6-x86_64 ^lcms@2.8%gcc@4.4.7 arch=linux-centos6-x86_64 ^libffi@3.2.1%gcc@4.4.7 arch=linux-centos6-x86_64 ^libmng@2.0.3%gcc@4.4.7 arch=linux-centos6-x86_64 ^libpng@1.6.29%gcc@4.4.7 arch=linux-centos6-x86_64 ^libpthread-stubs@0.4%gcc@4.4.7 arch=linux-centos6-x86_64 ^libtiff@4.0.8%gcc@4.4.7 arch=linux-centos6-x86_64 ^libx11@1.6.5%gcc@4.4.7 arch=linux-centos6-x86_64 ^libxau@1.0.8%gcc@4.4.7 arch=linux-centos6-x86_64 ^libxcb@1.12%gcc@4.4.7 arch=linux-centos6-x86_64 ^libxdmcp@1.1.2%gcc@4.4.7 arch=linux-centos6-x86_64 ^libxml2@2.9.4%gcc@4.4.7~python arch=linux-centos6-x86_64 ^ncurses@6.0%gcc@4.4.7~symlinks arch=linux-centos6-x86_64 ^openssl@1.0.2k%gcc@4.4.7 arch=linux-centos6-x86_64 ^pcre@8.40%gcc@4.4.7+utf arch=linux-centos6-x86_64 ^pkg-config@0.29.2%gcc@4.4.7+internal_glib arch=linux-centos6-x86_64 ^python@2.7.13%gcc@4.4.7+shared~tk~ucs4 arch=linux-centos6-x86_64 ^qt@5.9.1%gcc@4.4.7~dbus~examples~gtk~krellpatch~mesa+opengl~phonon~webkit arch=linux-centos6-x86_64 ^readline@7.0%gcc@4.4.7 arch=linux-centos6-x86_64 ^sqlite@3.8.10.2%gcc@4.4.7 arch=linux-centos6-x86_64 ^tar@1.29%gcc@4.4.7 arch=linux-centos6-x86_64 ^util-macros@1.19.1%gcc@4.4.7 arch=linux-centos6-x86_64 ^xcb-proto@1.12%gcc@4.4.7 arch=linux-centos6-x86_64 ^xextproto@7.3.0%gcc@4.4.7 arch=linux-centos6-x86_64 ^xproto@7.0.31%gcc@4.4.7 arch=linux-centos6-x86_64 ^xtrans@1.3.5%gcc@4.4.7 arch=linux-centos6-x86_64 ^xz@5.2.3%gcc@4.4.7 arch=linux-centos6-x86_64 ^zlib@1.2.11%gcc@4.4.7+pic+shared arch=linux-centos6-x86_64"
```
This error message isn't exactly helpful. It would be nice if we could do something like:
```python
conflicts('%gcc@:4.8', when='@4.3.0:', msg='Qt Creator 4.3.0+ requires a C++14 compiler')
```
This would make the error message much more helpful.


Discussion:

Resolving patch:
diff --git a/lib/spack/spack/directives.py b/lib/spack/spack/directives.py
--- a/lib/spack/spack/directives.py
+++ b/lib/spack/spack/directives.py
@@ -263,7 +263,7 @@ def _depends_on(pkg, spec, when=None, type=None):
 
 
 @directive('conflicts')
-def conflicts(conflict_spec, when=None):
+def conflicts(conflict_spec, when=None, msg=None):
     """Allows a package to define a conflict.
 
     Currently, a "conflict" is a concretized configuration that is known
@@ -280,14 +280,16 @@ def conflicts(conflict_spec, when=None):
     Args:
         conflict_spec (Spec): constraint defining the known conflict
         when (Spec): optional constraint that triggers the conflict
+        msg (str): optional user defined message
     """
     def _execute(pkg):
         # If when is not specified the conflict always holds
         condition = pkg.name if when is None else when
         when_spec = parse_anonymous_spec(condition, pkg.name)
 
+        # Save in a list the conflicts and the associated custom messages
         when_spec_list = pkg.conflicts.setdefault(conflict_spec, [])
-        when_spec_list.append(when_spec)
+        when_spec_list.append((when_spec, msg))
     return _execute
 
 
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -1797,9 +1797,9 @@ def concretize(self):
         for x in self.traverse():
             for conflict_spec, when_list in x.package.conflicts.items():
                 if x.satisfies(conflict_spec):
-                    for when_spec in when_list:
+                    for when_spec, msg in when_list:
                         if x.satisfies(when_spec):
-                            matches.append((x, conflict_spec, when_spec))
+                            matches.append((x, conflict_spec, when_spec, msg))
         if matches:
             raise ConflictsInSpecError(self, matches)
 
@@ -3437,8 +3437,24 @@ def __init__(self, spec, matches):
         message = 'Conflicts in concretized spec "{0}"\n'.format(
             spec.short_spec
         )
-        long_message = 'List of matching conflicts:\n\n'
-        match_fmt = '{0}. "{1}" conflicts with "{2}" in spec "{3}"\n'
-        for idx, (s, c, w) in enumerate(matches):
-            long_message += match_fmt.format(idx + 1, c, w, s)
+
+        visited = set()
+
+        long_message = ''
+
+        match_fmt_default = '{0}. "{1}" conflicts with "{2}"\n'
+        match_fmt_custom = '{0}. "{1}" conflicts with "{2}" [{3}]\n'
+
+        for idx, (s, c, w, msg) in enumerate(matches):
+
+            if s not in visited:
+                visited.add(s)
+                long_message += 'List of matching conflicts for spec:\n\n'
+                long_message += s.tree(indent=4) + '\n'
+
+            if msg is None:
+                long_message += match_fmt_default.format(idx + 1, c, w)
+            else:
+                long_message += match_fmt_custom.format(idx + 1, c, w, msg)
+
         super(ConflictsInSpecError, self).__init__(message, long_message)
diff --git a/var/spack/repos/builtin/packages/espresso/package.py b/var/spack/repos/builtin/packages/espresso/package.py
--- a/var/spack/repos/builtin/packages/espresso/package.py
+++ b/var/spack/repos/builtin/packages/espresso/package.py
@@ -75,8 +75,17 @@ class Espresso(Package):
     patch('dspev_drv_elpa.patch', when='@6.1 ^elpa@2016.05.003')
 
     # We can't ask for scalapack or elpa if we don't want MPI
-    conflicts('+scalapack', when='~mpi')
-    conflicts('+elpa', when='~mpi')
+    conflicts(
+        '+scalapack',
+        when='~mpi',
+        msg='scalapack is a parallel library and needs MPI support'
+    )
+
+    conflicts(
+        '+elpa',
+        when='~mpi',
+        msg='elpa is a parallel library and needs MPI support'
+    )
 
     # Elpa is formally supported by @:5.4.0, but QE configure searches
     # for it in the wrong folders (or tries to download it within


Test patch: