Repository: spack/spack

Issue:
spack generates modules with /usr/bin
spack generates modules with /usr/bin in them. When this modules are loaded they switch compilers.

here is the list of modules I got.
```
$ cd /soft/spack-0.10.1/share/spack/lmod/linux-centos7-x86_64/
[ssergien@beboplogin1 linux-centos7-x86_64]$ grep -R "/usr/bin" *
gcc/7.1.0/m4/1.4.16-epqs64j.lua:prepend_path("PATH", "/usr/bin")
gcc/7.1.0/autoconf/2.69-obpbcpi.lua:prepend_path("PATH", "/usr/bin")
gcc/7.1.0/bison/2.7-mjao2rh.lua:prepend_path("PATH", "/usr/bin")
gcc/7.1.0/flex/2.5.37-6pkw7dj.lua:prepend_path("PATH", "/usr/bin")
gcc/7.1.0/cmake/2.8.12.2-mh5v6sj.lua:prepend_path("PATH", "/usr/bin")
gcc/7.1.0/automake/1.13.4-kokvbmh.lua:prepend_path("PATH", "/usr/bin")
gcc/7.1.0/openssl/1.0.1e-nn3ng5d.lua:prepend_path("PATH", "/usr/bin")
gcc/7.1.0/libtool/2.4.2-lyjk4te.lua:prepend_path("PATH", "/usr/bin")
gcc/7.1.0/pkg-config/0.27.1-lyjdbtd.lua:prepend_path("PATH", "/usr/bin")
intel/17.0.3/bison/2.7-ifeqg3x.lua:prepend_path("PATH", "/usr/bin")
intel/17.0.3/openssl/1.0.1e-afp44aa.lua:prepend_path("PATH", "/usr/bin")
intel/17.0.3/libtool/2.4.2-zrj7kuz.lua:prepend_path("PATH", "/usr/bin")
intel/17.0.3/libtool/2.4.2-xzrrrf5.lua:prepend_path("PATH", "/usr/bin")
intel/17.0.3/pkg-config/0.27.1-azeappb.lua:prepend_path("PATH", "/usr/bin")
intel/17.0.3/pkg-config/0.27.1-or5h6mu.lua:prepend_path("PATH", "/usr/bin")
intel/17.0.4/m4/1.4.16-4mceggk.lua:prepend_path("PATH", "/usr/bin")
intel/17.0.4/autoconf/2.69-potli3m.lua:prepend_path("PATH", "/usr/bin")
intel/17.0.4/bison/2.7-m5h6nq4.lua:prepend_path("PATH", "/usr/bin")
intel/17.0.4/flex/2.5.37-mdp4grx.lua:prepend_path("PATH", "/usr/bin")
intel/17.0.4/automake/1.13.4-a2fyvjt.lua:prepend_path("PATH", "/usr/bin")
intel/17.0.4/openssl/1.0.1e-cdz677p.lua:prepend_path("PATH", "/usr/bin")
intel/17.0.4/libtool/2.4.2-f5ckt6h.lua:prepend_path("PATH", "/usr/bin")
intel/17.0.4/pkg-config/0.27.1-tkab33h.lua:prepend_path("PATH", "/usr/bin")
```
That is probably coming from this inside of packages.yaml:
```
autoconf:
    paths:
      autoconf@2.69 arch=linux-centos7-x86_64: /usr
      autoconf@2.63 arch=linux-centos6-x86_64: /usr
    buildable: False
  automake:
    paths:
      automake@1.13.4 arch=linux-centos7-x86_64: /usr
      automake@1.11.1 arch=linux-centos6-x86_64: /usr
    buildable: False
  bison:
    paths:
      bison@2.7   arch=linux-centos7-x86_64: /usr
      bison@2.4.1 arch=linux-centos6-x86_64: /usr
    buildable: False
  cmake:
    paths:
      cmake@2.8.12.2 arch=linux-centos6-x86_64: /usr
  flex:
    paths:
      flex@2.5.37 arch=linux-centos7-x86_64: /usr
      flex@2.5.35 arch=linux-centos6-x86_64: /usr
    buildable: False
  libtool:
    paths:
      libtool@2.4.2 arch=linux-centos7-x86_64: /usr
      libtool@2.2.6b arch=linux-centos6-x86_64: /usr
    buildable: False
  m4:
    paths:
      m4@1.4.16 arch=linux-centos7-x86_64: /usr
      m4@1.4.13 arch=linux-centos6-x86_64: /usr
    buildable: False
```
```$ spack find -p m4
==> 6 installed packages.
-- linux-centos6-x86_64 / gcc@6.1.0 -----------------------------
    m4@1.4.13  /usr

-- linux-centos6-x86_64 / intel@17.0.0 --------------------------
    m4@1.4.13  /usr

-- linux-centos7-x86_64 / gcc@4.8.5 -----------------------------
    m4@1.4.16  /usr

-- linux-centos7-x86_64 / gcc@7.1.0 -----------------------------
    m4@1.4.16  /usr

-- linux-centos7-x86_64 / intel@17.0.4 --------------------------
    m4@1.4.16  /usr
    m4@1.4.18  /blues/gpfs/home/software/spack-0.10.1/opt/spack/linux-centos7-x86_64/intel-17.0.4/m4-1.4.18-vytvqhc2wpwfxvqnjisv6z5pd756nenw
```
I think this modules should not be generated. 

Or at least do `append_path` vs `prepend_path` 

Thanks






Discussion:
> spack generates modules with /usr/bin in them. When this modules are loaded they switch compilers.

That's what *you* configured Spack to do. Note that you can prevent module files from being generated if you blacklist them. Something like:
```yaml
modules:
  lmod:
    blacklist:
      - autoconf
      - automake
      - bison
      ...
```
You'll find examples on how to do that [here](https://spack.readthedocs.io/en/latest/tutorial_modules.html#prevent-some-module-files-from-being-generated) and [here](https://spack.readthedocs.io/en/latest/module_file_support.html#blacklist-or-whitelist-the-generation-of-specific-module-files) in the docs.
So why is spack shows that m4 was installed with gcc@7.1.0:
```
-- linux-centos7-x86_64 / gcc@7.1.0 -----------------------------
    m4@1.4.16  /usr
```
Is it because of:
```
buildable: False
```
in packages.yaml?

We also have this in modules.yaml:
```
core_compilers:
      - 'gcc@4.8.5'
    blacklist:
      - '%gcc@4.8.5'
    whitelist:
      - gcc
      - intel
      - intel-parallel-studio
      - nag
      - pgi
      - cmake
```
And it is all true all these packages were installed wit gcc@4.8.5.
 and it suppose not to create modules for them.

It is probably because if this:
```linux-centos7-x86_64 / gcc@7.1.0 -----------------------------
    m4@1.4.16  /usr

-- linux-centos7-x86_64 / intel@17.0.4 --------------------------
    m4@1.4.16  /usr
```
Why spack thinks that these were installed with different compilers?

Thanks.




When you install something, Spack performs a *concretization* of the spec you asked for. One of the rules applied in the concretization process is that, by default, all the specs in a DAG must be compiled with the same compiler. You can see what will be installed using the command:
```console
spack spec -Il <spec>
```

Now, when you configure an external package you give a set of constraints like:
```yaml
packages:
  autoconf:
    paths:
      autoconf@2.69 arch=linux-centos7-x86_64: /usr
      autoconf@2.63 arch=linux-centos6-x86_64: /usr
    buildable: False
```
Here nothing is said about the compiler, so when Spack needs to install `autoconf@2.69 arch=linux-centos7-x86_64 %gcc@7.1.0` it assumes it's installed externally in `/usr`. 

---

All the above is to explain the logic behind it. To solve your "practical" issue you need to:
```console
spack module rm -m lmod <external_spec>
```
and then add these external packages into the blacklist of `modules.yaml`.
If I understand correctly, would giving it a compiler constraint in packages.yaml:
```packages:
  autoconf:
    paths:
      autoconf@2.69 %gcc@4.8.5 arch=linux-centos7-x86_64: /usr
      autoconf@2.63 arch=linux-centos6-x86_64: /usr
    buildable: False
```
accomplish same result?

I talked to @ssergien about this (I'm the one who set up his `packages.yaml` with those external packages). I think Spack is doing the wrong thing here, and I consider this a bug. There are many places in Spack that we jump through hoops to make sure `/bin`, `/usr/bin`, and `/usr/local/bin` don't accidentally get added to the `PATH` during build-time. We should also be doing this when writing module files for external packages.

Resolving patch:
diff --git a/lib/spack/spack/environment.py b/lib/spack/spack/environment.py
--- a/lib/spack/spack/environment.py
+++ b/lib/spack/spack/environment.py
@@ -502,25 +502,56 @@ def filter_environment_blacklist(env, variables):
             yield item
 
 
-def inspect_path(root, inspections):
-    """Inspects a path to search for the subdirectories specified in the
-    inspection dictionary. Return a list of commands that will modify the
-    environment accordingly.
+def inspect_path(root, inspections, exclude=None):
+    """Inspects ``root`` to search for the subdirectories in ``inspections``.
+    Adds every path found to a list of prepend-path commands and returns it.
 
     Args:
-        root: path where to search for subdirectories
-        inspections: dictionary that maps subdirectories to a list of
-            variables that we want to pre-pend with a path, if found
+        root (str): absolute path where to search for subdirectories
+
+        inspections (dict): maps relative paths to a list of environment
+            variables that will be modified if the path exists. The
+            modifications are not performed immediately, but stored in a
+            command object that is returned to client
+
+        exclude (callable): optional callable. If present it must accept an
+            absolute path and return True if it should be excluded from the
+            inspection
+
+    Examples:
+
+    The following lines execute an inspection in ``/usr`` to search for
+    ``/usr/include`` and ``/usr/lib64``. If found we want to prepend
+    ``/usr/include`` to ``CPATH`` and ``/usr/lib64`` to ``MY_LIB64_PATH``.
+
+        .. code-block:: python
+
+            # Set up the dictionary containing the inspection
+            inspections = {
+                'include': ['CPATH'],
+                'lib64': ['MY_LIB64_PATH']
+            }
+
+            # Get back the list of command needed to modify the environment
+            env = inspect_path('/usr', inspections)
+
+            # Eventually execute the commands
+            env.apply_modifications()
 
     Returns:
         instance of EnvironmentModifications containing the requested
         modifications
     """
+    if exclude is None:
+        exclude = lambda x: False
+
     env = EnvironmentModifications()
     # Inspect the prefix to check for the existence of common directories
     for relative_path, variables in inspections.items():
         expected = os.path.join(root, relative_path)
-        if os.path.isdir(expected):
+
+        if os.path.isdir(expected) and not exclude(expected):
             for variable in variables:
                 env.prepend_path(variable, expected)
+
     return env
diff --git a/lib/spack/spack/modules/common.py b/lib/spack/spack/modules/common.py
--- a/lib/spack/spack/modules/common.py
+++ b/lib/spack/spack/modules/common.py
@@ -61,6 +61,7 @@
 import spack.environment
 import spack.tengine as tengine
 import spack.util.path
+import spack.util.environment
 import spack.error
 
 #: Root folders where the various module files should be written
@@ -473,7 +474,9 @@ def environment_modifications(self):
         """List of environment modifications to be processed."""
         # Modifications guessed inspecting the spec prefix
         env = spack.environment.inspect_path(
-            self.spec.prefix, prefix_inspections
+            self.spec.prefix,
+            prefix_inspections,
+            exclude=spack.util.environment.is_system_path
         )
 
         # Modifications that are coded at package level
diff --git a/lib/spack/spack/util/environment.py b/lib/spack/spack/util/environment.py
--- a/lib/spack/spack/util/environment.py
+++ b/lib/spack/spack/util/environment.py
@@ -30,8 +30,21 @@
     system_paths
 
 
+def is_system_path(path):
+    """Predicate that given a path returns True if it is a system path,
+    False otherwise.
+
+    Args:
+        path (str): path to a directory
+
+    Returns:
+        True or False
+    """
+    return os.path.normpath(path) in system_dirs
+
+
 def filter_system_paths(paths):
-    return [p for p in paths if os.path.normpath(p) not in system_dirs]
+    return [p for p in paths if not is_system_path(p)]
 
 
 def get_path(name):


Test patch:
diff --git a/lib/spack/spack/test/environment.py b/lib/spack/spack/test/environment.py
--- a/lib/spack/spack/test/environment.py
+++ b/lib/spack/spack/test/environment.py
@@ -30,7 +30,7 @@
 from spack.environment import EnvironmentModifications
 from spack.environment import RemovePath, PrependPath, AppendPath
 from spack.environment import SetEnv, UnsetEnv
-from spack.util.environment import filter_system_paths
+from spack.util.environment import filter_system_paths, is_system_path
 
 
 def test_inspect_path(tmpdir):
@@ -60,6 +60,20 @@ def test_inspect_path(tmpdir):
     assert 'CPATH' in names
 
 
+def test_exclude_paths_from_inspection():
+    inspections = {
+        'lib': ['LIBRARY_PATH', 'LD_LIBRARY_PATH'],
+        'lib64': ['LIBRARY_PATH', 'LD_LIBRARY_PATH'],
+        'include': ['CPATH']
+    }
+
+    env = environment.inspect_path(
+        '/usr', inspections, exclude=is_system_path
+    )
+
+    assert len(env) == 0
+
+
 @pytest.fixture()
 def prepare_environment_for_tests():
     """Sets a few dummy variables in the current environment, that will be