Module Files

In this tutorial, we’ll introduce a few concepts that are fundamental to the generation of module files with Spack, and we’ll guide you through the customization of both module files content and their layout on disk. In the end you should have a clear understanding of:

  • What are module files and how they work
  • How Spack generates them
  • Which commands are available to ease their maintenance
  • How it is possible to customize them in all aspects

Modules at a glance

Let’s start by summarizing what module files are and how you can use them to modify your environment. The idea is to give enough information so that people without any previous exposure to them will be able to follow the tutorial later on. We’ll also give a high-level view of how module files are generated in Spack. If you are already familiar with these topics you can quickly skim through this section or move directly to Setup for the tutorial.

What are module files?

Module files are an easy way to modify your environment in a controlled manner during a shell session. In general, they contain the information needed to run an application or use a library, and they work in conjunction with a tool that interprets them. Typical module files instruct this tool to modify the environment variables when a module file is loaded:

$ module show zlib
-------------------------------------------------------------------
/home/mculpo/PycharmProjects/spack/share/spack/modules/linux-ubuntu14.04-x86_64/zlib/1.2.11-gcc-7.2.0-linux-ubuntu14.04-x86_64-co2px3k:

module-whatis       A free, general-purpose, legally unencumbered lossless data-compression library.
prepend-path        MANPATH /home/mculpo/PycharmProjects/spack/opt/spack/linux-ubuntu14.04-x86_64/gcc-7.2.0/zlib-1.2.11-co2px3k53m76lm6tofylh2mur2hnicux/share/man
prepend-path        LIBRARY_PATH /home/mculpo/PycharmProjects/spack/opt/spack/linux-ubuntu14.04-x86_64/gcc-7.2.0/zlib-1.2.11-co2px3k53m76lm6tofylh2mur2hnicux/lib
prepend-path        LD_LIBRARY_PATH /home/mculpo/PycharmProjects/spack/opt/spack/linux-ubuntu14.04-x86_64/gcc-7.2.0/zlib-1.2.11-co2px3k53m76lm6tofylh2mur2hnicux/lib
prepend-path        CPATH /home/mculpo/PycharmProjects/spack/opt/spack/linux-ubuntu14.04-x86_64/gcc-7.2.0/zlib-1.2.11-co2px3k53m76lm6tofylh2mur2hnicux/include
prepend-path        PKG_CONFIG_PATH /home/mculpo/PycharmProjects/spack/opt/spack/linux-ubuntu14.04-x86_64/gcc-7.2.0/zlib-1.2.11-co2px3k53m76lm6tofylh2mur2hnicux/lib/pkgconfig
prepend-path        CMAKE_PREFIX_PATH /home/mculpo/PycharmProjects/spack/opt/spack/linux-ubuntu14.04-x86_64/gcc-7.2.0/zlib-1.2.11-co2px3k53m76lm6tofylh2mur2hnicux/
-------------------------------------------------------------------

$ echo $LD_LIBRARY_PATH

$ module load zlib
$ echo $LD_LIBRARY_PATH
/home/mculpo/PycharmProjects/spack/opt/spack/linux-ubuntu14.04-x86_64/gcc-7.2.0/zlib-1.2.11-co2px3k53m76lm6tofylh2mur2hnicux/lib

and to undo the modifications when the same module file is unloaded:

$ module unload zlib
$ echo $LD_LIBRARY_PATH

$

Different formats exist for module files, and different tools provide various levels of support for them. Spack can natively generate:

  1. Non-hierarchical module files written in TCL
  2. Hierarchical module files written in Lua

and can build environment-modules and lmod as support tools. Which of the formats or tools best suits one’s needs depends on each particular use-case. For the sake of illustration, we’ll be working on both formats using lmod.

See also

Environment modules
This is the original tool that provided modules support. Its first version was coded in C in the early ’90s and was later substituted by a version completely coded in TCL - the one Spack is distributing. More details on its features are given in the homepage of the project or in its github page. The tool is able to interpret the non-hierarchical TCL modulefiles written by Spack.
Lmod
Lmod is a module system written in Lua, designed to easily handle hierarchies of module files. It’s a drop-in replacement of Environment Modules and works with both of the module file formats generated by Spack. Despite being fully compatible with Environment Modules there are many features that are unique to Lmod. These features are either targeted towards safety or meant to extend the module system functionality.

How do we generate module files?

Before we dive into the hands-on sections it’s worth spending a couple of words to explain how module files are generated by Spack. The following diagram provides a high-level view of the process:

_images/module_file_generation.svg

The red dashed line above represents Spack’s boundaries, the blue one Spack’s dependencies [1]. Module files are generated by combining:

  • the configuration details in config.yaml and modules.yaml
  • the information contained in Spack packages (and processed by the module subpackage)
  • a set of template files

with Jinja2, an external template engine that stamps out each particular module file. As Spack serves very diverse needs this process has many points of customization, and we’ll explore most of them in the next sections.

[1]Spack vendors its dependencies! This means that Spack comes with a copy of each one of its dependencies, including Jinja2, and is already configured to use them.

Setup for the tutorial

In order to showcase the capabilities of Spack’s module file generation, we need a representative set of software to work with. This set must include different flavors of the same packages installed alongside each other and some external packages.

The purpose of this setup is not to make our life harder but to demonstrate how Spack can help with similar situations, as they will happen on real HPC clusters. For instance, it’s often preferable for Spack to use vendor-provided MPI implementations than to build one itself.

The best way to follow along is to use a Docker image, which comes with Spack and all the software used in the following parts already pre-installed. If you want to proceed this way, read Use a Docker image.

If you don’t have Docker installed or for any other reason you prefer to work locally, follow instead Work in a local folder to know how to clone Spack and install the software. Be aware that the set-up will take longer and that the details of the snippets below assume the Docker image and may need changes to work in your particular environment.

Use a Docker image

The fastest way to set-up your environment is to use a Docker image:

$ docker pull spack/module-tutorial:latest
$ docker run --rm -h module-file-tutorial -it spack/module-tutorial:latest
root@module-file-tutorial:/#

If you arrived at this point you should be ready to start, as all the software needed is pre-installed in the image:

root@module-file-tutorial:/# which spack
/usr/local/bin/spack
root@module-file-tutorial:/# spack find
==> 43 installed packages.
-- linux-ubuntu16.04-x86_64 / gcc@5.4.0 -------------------------
autoconf@2.69    gdbm@1.14.1  isl@0.19         lmod@7.7.29              lua-luaposix@33.4.0  mpfr@4.0.1   pkgconf@1.4.2  zlib@1.2.11
automake@1.16.1  git@2.9.4    libsigsegv@2.11  lua@5.3.4                m4@1.4.18            ncurses@6.1  readline@7.0
gcc@7.2.0        gmp@6.1.2    libtool@2.4.6    lua-luafilesystem@1_6_3  mpc@1.1.0            perl@5.26.2  tcl@8.6.8

-- linux-ubuntu16.04-x86_64 / gcc@7.2.0 -------------------------
bzip2@1.0.6   mpich@3.2.1          netlib-scalapack@2.0.2  netlib-scalapack@2.0.2  openssl@1.0.2o   py-scipy@1.1.0        readline@7.0
cmake@3.12.0  ncurses@6.1          netlib-scalapack@2.0.2  openblas@0.3.0          pkgconf@1.4.2    py-setuptools@39.2.0  sqlite@3.23.1
gdbm@1.14.1   netlib-lapack@3.8.0  netlib-scalapack@2.0.2  openmpi@1.10.2          py-numpy@1.14.3  python@2.7.15         zlib@1.2.11

Go to Non-hierarchical module files to proceed with the tutorial.

Note

Dockerfile for this image
Those of you that want to build a similar container themselves can find the Dockerfile and the other resources in Spack’s share/spack/docs/docker folder.

Work in a local folder

If you don’t feel like using a container, you can set-up your environment locally. Let’s start by cloning the Spack repository and moving to the directory where it was checked out:

$ git clone https://github.com/spack/spack.git
$ cd spack

From here we’ll be building the required stack of software.

Build a module tool

The first thing that we need is the module tool. In this case we choose lmod as it can work with both hierarchical and non-hierarchical module file layouts.

$ bin/spack install lmod

Once the module tool is installed we need to have it available in the current shell. As the installation directories are definitely not easy to remember, we’ll employ the command spack location to retrieve the lmod prefix directly from Spack:

$ . $(spack location -i lmod)/lmod/lmod/init/bash

Now we can source the setup file and activate the shell support:

$ . share/spack/setup_env.sh

Add a new compiler

The second step is to build a recent compiler. On first use, Spack scans the environment and automatically locates the compiler(s) already available on the system. This is what you’ll see on Ubuntu 14.04:

$ uname -a
Linux nuvolari 4.4.0-45-generic #66~14.04.1-Ubuntu SMP Wed Oct 19 15:05:38 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

$ spack compilers
==> Available compilers
-- gcc ----------------------------------------------------------
gcc@4.8

Let’s bootstrap a more recent compiler with the one that was automatically detected:

$ spack install gcc@7.2.0
...
Wait a long time
...

Once gcc is installed we can use shell support to load it and make it readily available:

$ spack load gcc@7.2.0

It may not be apparent, but the last command employed the module files generated automatically by Spack. What happens under the hood when you use the spack load command is:

  1. the spec passed as argument is translated into a module file name
  2. the current module tool is used to load that module file

You can use this command to double check:

$ module list
Currently Loaded Modules:
1) lmod-7.7-gcc-4.8-okcwjgw 2) gcc-7.2.0-gcc-4.8-twd5nqg

Note that the 7-digit hash at the end of the generated module may vary depending on architecture or package version. Now that we have gcc@7.2.0 in PATH we can finally add it to the list of compilers known to Spack:

$ spack compiler add
==> Added 1 new compiler to ~/.spack/linux/compilers.yaml
    gcc@7.2.0

$ spack compilers
==> Available compilers
-- gcc ----------------------------------------------------------
gcc@7.2.0  gcc@4.8

Build the software that will be used in the tutorial

The last step is to install the software stack needed later on. To mimic an external installation of an MPI provider we’ll install openmpi on the system we are working on. On Ubuntu 14.04 it boils down to:

$ sudo apt-get install openmpi-bin openmpi-common libopenmpi-dev
...

but the exact command varies according to your OS. Then we need to prepare a packages.yaml file that instructs Spack to use an externally provided MPI:

packages:
  openmpi:
    buildable: False
    paths:
      openmpi@1.6: /usr

Finally, we should use Spack to install the packages used in the examples:

$ spack install netlib-scalapack ^openmpi ^openblas
$ spack install netlib-scalapack ^mpich ^openblas
$ spack install netlib-scalapack ^openmpi ^netlib-lapack
$ spack install netlib-scalapack ^mpich ^netlib-lapack
$ spack install py-scipy ^openblas

Non-hierarchical module files

If you arrived to this point you should have an environment that looks similar to:

root@module-file-tutorial:/# module avail

-------------------------------------------- /usr/local/share/spack/modules/linux-ubuntu16.04-x86_64 ---------------------------------------------
   autoconf-2.69-gcc-5.4.0-cbvv5rj              lua-luaposix-33.4.0-gcc-5.4.0-i7w7ynf       perl-5.26.2-gcc-5.4.0-n2k4mza
   automake-1.16.1-gcc-5.4.0-lykrrr4            m4-1.4.18-gcc-5.4.0-3z33ecf                 pkgconf-1.4.2-gcc-5.4.0-fovrh7a
   bzip2-1.0.6-gcc-7.2.0-mwamumj                mpc-1.1.0-gcc-5.4.0-56lbd3h                 pkgconf-1.4.2-gcc-7.2.0-yoxwmgb
   cmake-3.12.0-gcc-7.2.0-6ovorxs               mpfr-4.0.1-gcc-5.4.0-dy5r7hi                py-numpy-1.14.3-gcc-7.2.0-t3loxvu
   gcc-7.2.0-gcc-5.4.0-wwhgyej                  mpich-3.2.1-gcc-7.2.0-vt5xcat               py-scipy-1.1.0-gcc-7.2.0-fdiryak
   gdbm-1.14.1-gcc-5.4.0-q4fpyuo                ncurses-6.1-gcc-5.4.0-3o765ou               py-setuptools-39.2.0-gcc-7.2.0-jqhycal
   gdbm-1.14.1-gcc-7.2.0-zk5lhob                ncurses-6.1-gcc-7.2.0-xcgzqdv               python-2.7.15-gcc-7.2.0-c7pnzul
   git-2.9.4-gcc-5.4.0-mkaoyhz                  netlib-lapack-3.8.0-gcc-7.2.0-7apabqu       readline-7.0-gcc-5.4.0-nxhwrg7
   gmp-6.1.2-gcc-5.4.0-qc4qcfz                  netlib-scalapack-2.0.2-gcc-7.2.0-3bz5rxx    readline-7.0-gcc-7.2.0-ccruj2i
   isl-0.19-gcc-5.4.0-hsl7f52                   netlib-scalapack-2.0.2-gcc-7.2.0-6i5qsqx    sqlite-3.23.1-gcc-7.2.0-5ltus3a
   libsigsegv-2.11-gcc-5.4.0-fypapcp            netlib-scalapack-2.0.2-gcc-7.2.0-uhzmwog    tcl-8.6.8-gcc-5.4.0-qhwyccy
   libtool-2.4.6-gcc-5.4.0-o2pfwjf              netlib-scalapack-2.0.2-gcc-7.2.0-z52ltyy    zlib-1.2.11-gcc-5.4.0-5nus6kn
   lmod-7.7.29-gcc-5.4.0-wl6mywv                openblas-0.3.0-gcc-7.2.0-pdatzbi            zlib-1.2.11-gcc-7.2.0-ezuwp4p
   lua-5.3.4-gcc-5.4.0-izvaota                  openmpi-1.10.2-gcc-7.2.0-6oewzwj
   lua-luafilesystem-1_6_3-gcc-5.4.0-ywlmaou    openssl-1.0.2o-gcc-7.2.0-cvldq3v

Use "module spider" to find all possible modules.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".

The non-hierarchical module files that have been generated so far follow the default rules for module generation. Taking a look at the gcc module you’ll see, for example:

root@module-file-tutorial:/# module show gcc-7.2.0-gcc-5.4.0-wwhgyej
----------------------------------------------------------------------------------------------------------------------------------------------
   /usr/local/share/spack/modules/linux-ubuntu16.04-x86_64/gcc-7.2.0-gcc-5.4.0-wwhgyej:
----------------------------------------------------------------------------------------------------------------------------------------------
whatis("The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Ada, and Go, as well as libraries for these languages. ")
prepend_path("PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin")
prepend_path("MANPATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/share/man")
prepend_path("LIBRARY_PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/lib")
prepend_path("LD_LIBRARY_PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/lib")
prepend_path("LIBRARY_PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/lib64")
prepend_path("LD_LIBRARY_PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/lib64")
prepend_path("CPATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/include")
prepend_path("CMAKE_PREFIX_PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/")
setenv("CC","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gcc")
setenv("CXX","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/g++")
setenv("FC","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gfortran")
setenv("F77","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gfortran")
setenv("F90","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gfortran")
help([[The GNU Compiler Collection includes front ends for C, C++, Objective-C,
Fortran, Ada, and Go, as well as libraries for these languages.
]])

As expected, a few environment variables representing paths will be modified by the module file according to the default prefix inspection rules.

Filter unwanted modifications to the environment

Now consider the case that your site has decided that CPATH and LIBRARY_PATH modifications should not be present in module files. What you can do to abide by the rules is to create a configuration file ~/.spack/modules.yaml with the following content:

modules:
  tcl:
    all:
      filter:
        environment_blacklist: ['CPATH', 'LIBRARY_PATH']

Next you should regenerate all the module files:

root@module-file-tutorial:/# spack module tcl refresh
==> You are about to regenerate tcl module files for:

-- linux-ubuntu16.04-x86_64 / gcc@5.4.0 -------------------------
cbvv5rj autoconf@2.69    qc4qcfz gmp@6.1.2        izvaota lua@5.3.4                dy5r7hi mpfr@4.0.1     qhwyccy tcl@8.6.8
lykrrr4 automake@1.16.1  hsl7f52 isl@0.19         ywlmaou lua-luafilesystem@1_6_3  3o765ou ncurses@6.1    5nus6kn zlib@1.2.11
wwhgyej gcc@7.2.0        fypapcp libsigsegv@2.11  i7w7ynf lua-luaposix@33.4.0      n2k4mza perl@5.26.2
q4fpyuo gdbm@1.14.1      o2pfwjf libtool@2.4.6    3z33ecf m4@1.4.18                fovrh7a pkgconf@1.4.2
mkaoyhz git@2.9.4        wl6mywv lmod@7.7.29      56lbd3h mpc@1.1.0                nxhwrg7 readline@7.0

-- linux-ubuntu16.04-x86_64 / gcc@7.2.0 -------------------------
mwamumj bzip2@1.0.6   7apabqu netlib-lapack@3.8.0     pdatzbi openblas@0.3.0   fdiryak py-scipy@1.1.0        ezuwp4p zlib@1.2.11
6ovorxs cmake@3.12.0  6i5qsqx netlib-scalapack@2.0.2  6oewzwj openmpi@1.10.2   jqhycal py-setuptools@39.2.0
zk5lhob gdbm@1.14.1   uhzmwog netlib-scalapack@2.0.2  cvldq3v openssl@1.0.2o   c7pnzul python@2.7.15
vt5xcat mpich@3.2.1   3bz5rxx netlib-scalapack@2.0.2  yoxwmgb pkgconf@1.4.2    ccruj2i readline@7.0
xcgzqdv ncurses@6.1   z52ltyy netlib-scalapack@2.0.2  t3loxvu py-numpy@1.14.3  5ltus3a sqlite@3.23.1

==> Do you want to proceed? [y/n] y
==> Regenerating tcl module files

If you take a look now at the module for gcc you’ll see that the unwanted paths have disappeared:

root@module-file-tutorial:/# module show gcc-7.2.0-gcc-5.4.0-wwhgyej
----------------------------------------------------------------------------------------------------------------------------------------------
   /usr/local/share/spack/modules/linux-ubuntu16.04-x86_64/gcc-7.2.0-gcc-5.4.0-wwhgyej:
----------------------------------------------------------------------------------------------------------------------------------------------
whatis("The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Ada, and Go, as well as libraries for these languages. ")
prepend_path("PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin")
prepend_path("MANPATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/share/man")
prepend_path("LD_LIBRARY_PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/lib")
prepend_path("LD_LIBRARY_PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/lib64")
prepend_path("CMAKE_PREFIX_PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/")
setenv("CC","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gcc")
setenv("CXX","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/g++")
setenv("FC","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gfortran")
setenv("F77","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gfortran")
setenv("F90","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gfortran")
help([[The GNU Compiler Collection includes front ends for C, C++, Objective-C,
Fortran, Ada, and Go, as well as libraries for these languages.
]])

Prevent some module files from being generated

Another common request at many sites is to avoid exposing software that is only needed as an intermediate step when building a newer stack. Let’s try to prevent the generation of module files for anything that is compiled with gcc@5.4.0 (the OS provided compiler).

To do this you should add a blacklist keyword to ~/.spack/modules.yaml:

modules:
  tcl:
    blacklist:
      -  '%gcc@5.4.0'
    all:
      filter:
        environment_blacklist: ['CPATH', 'LIBRARY_PATH']

and regenerate the module files:

root@module-file-tutorial:/# spack module tcl refresh --delete-tree
==> You are about to regenerate tcl module files for:

-- linux-ubuntu16.04-x86_64 / gcc@5.4.0 -------------------------
cbvv5rj autoconf@2.69    qc4qcfz gmp@6.1.2        izvaota lua@5.3.4                dy5r7hi mpfr@4.0.1     qhwyccy tcl@8.6.8
lykrrr4 automake@1.16.1  hsl7f52 isl@0.19         ywlmaou lua-luafilesystem@1_6_3  3o765ou ncurses@6.1    5nus6kn zlib@1.2.11
wwhgyej gcc@7.2.0        fypapcp libsigsegv@2.11  i7w7ynf lua-luaposix@33.4.0      n2k4mza perl@5.26.2
q4fpyuo gdbm@1.14.1      o2pfwjf libtool@2.4.6    3z33ecf m4@1.4.18                fovrh7a pkgconf@1.4.2
mkaoyhz git@2.9.4        wl6mywv lmod@7.7.29      56lbd3h mpc@1.1.0                nxhwrg7 readline@7.0

-- linux-ubuntu16.04-x86_64 / gcc@7.2.0 -------------------------
mwamumj bzip2@1.0.6   7apabqu netlib-lapack@3.8.0     pdatzbi openblas@0.3.0   fdiryak py-scipy@1.1.0        ezuwp4p zlib@1.2.11
6ovorxs cmake@3.12.0  6i5qsqx netlib-scalapack@2.0.2  6oewzwj openmpi@1.10.2   jqhycal py-setuptools@39.2.0
zk5lhob gdbm@1.14.1   uhzmwog netlib-scalapack@2.0.2  cvldq3v openssl@1.0.2o   c7pnzul python@2.7.15
vt5xcat mpich@3.2.1   3bz5rxx netlib-scalapack@2.0.2  yoxwmgb pkgconf@1.4.2    ccruj2i readline@7.0
xcgzqdv ncurses@6.1   z52ltyy netlib-scalapack@2.0.2  t3loxvu py-numpy@1.14.3  5ltus3a sqlite@3.23.1

==> Do you want to proceed? [y/n] y
==> Regenerating tcl module files

root@module-file-tutorial:/# module avail

-------------------------------------------- /usr/local/share/spack/modules/linux-ubuntu16.04-x86_64 ---------------------------------------------
   bzip2-1.0.6-gcc-7.2.0-mwamumj               netlib-scalapack-2.0.2-gcc-7.2.0-6i5qsqx    py-numpy-1.14.3-gcc-7.2.0-t3loxvu
   cmake-3.12.0-gcc-7.2.0-6ovorxs              netlib-scalapack-2.0.2-gcc-7.2.0-uhzmwog    py-scipy-1.1.0-gcc-7.2.0-fdiryak
   gdbm-1.14.1-gcc-7.2.0-zk5lhob               netlib-scalapack-2.0.2-gcc-7.2.0-z52ltyy    py-setuptools-39.2.0-gcc-7.2.0-jqhycal
   mpich-3.2.1-gcc-7.2.0-vt5xcat               openblas-0.3.0-gcc-7.2.0-pdatzbi            python-2.7.15-gcc-7.2.0-c7pnzul
   ncurses-6.1-gcc-7.2.0-xcgzqdv               openmpi-1.10.2-gcc-7.2.0-6oewzwj            readline-7.0-gcc-7.2.0-ccruj2i
   netlib-lapack-3.8.0-gcc-7.2.0-7apabqu       openssl-1.0.2o-gcc-7.2.0-cvldq3v            sqlite-3.23.1-gcc-7.2.0-5ltus3a
   netlib-scalapack-2.0.2-gcc-7.2.0-3bz5rxx    pkgconf-1.4.2-gcc-7.2.0-yoxwmgb             zlib-1.2.11-gcc-7.2.0-ezuwp4p

Use "module spider" to find all possible modules.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".

This time it is convenient to pass the option --delete-tree to the command that regenerates the module files to instruct it to delete the existing tree and regenerate a new one instead of overwriting the files in the existing directory.

If you look closely you’ll see though that we went too far in blacklisting modules: the module for gcc@7.2.0 disappeared as it was bootstrapped with gcc@5.4.0. To specify exceptions to the blacklist rules you can use whitelist:

modules:
  tcl:
    whitelist:
      -  gcc
    blacklist:
      -  '%gcc@5.4.0'
    all:
      filter:
        environment_blacklist: ['CPATH', 'LIBRARY_PATH']

whitelist rules always have precedence over blacklist rules. If you regenerate the modules again:

root@module-file-tutorial:/# spack module tcl refresh -y
==> Regenerating tcl module files

you’ll see that now the module for gcc@7.2.0 has reappeared:

root@module-file-tutorial:/# module av gcc-7.2.0-gcc-5.4.0-wwhgyej

-------------------------------------------- /usr/local/share/spack/modules/linux-ubuntu16.04-x86_64 ---------------------------------------------
   gcc-7.2.0-gcc-5.4.0-wwhgyej

Use "module spider" to find all possible modules.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".

An additional possibility that you can leverage to unclutter the environment is that of preventing the generation of module files for implicitly installed packages. In this case all one needs to do is to add the following line:

modules:
  tcl:
    blacklist_implicits: true
    whitelist:
      -  gcc
    blacklist:
      -  '%gcc@5.4.0'
    all:
      filter:
        environment_blacklist: ['CPATH', 'LIBRARY_PATH']

to modules.yaml and regenerate the module file tree as above.

Change module file naming

The next step in making module files more user-friendly is to improve their naming scheme. To reduce the length of the hash or remove it altogether you can use the hash_length keyword in the configuration file:

modules:
  tcl:
    hash_length: 0
    whitelist:
      -  gcc
    blacklist:
      -  '%gcc@5.4.0'
    all:
      filter:
        environment_blacklist: ['CPATH', 'LIBRARY_PATH']

If you try to regenerate the module files now you will get an error:

root@module-file-tutorial:/# spack module tcl refresh --delete-tree -y
==> Error: Name clashes detected in module files:

file: /usr/local/share/spack/modules/linux-ubuntu16.04-x86_64/netlib-scalapack-2.0.2-gcc-7.2.0
spec: netlib-scalapack@2.0.2%gcc@7.2.0 build_type=RelWithDebInfo ~pic+shared arch=linux-ubuntu16.04-x86_64
spec: netlib-scalapack@2.0.2%gcc@7.2.0 build_type=RelWithDebInfo ~pic+shared arch=linux-ubuntu16.04-x86_64
spec: netlib-scalapack@2.0.2%gcc@7.2.0 build_type=RelWithDebInfo ~pic+shared arch=linux-ubuntu16.04-x86_64
spec: netlib-scalapack@2.0.2%gcc@7.2.0 build_type=RelWithDebInfo ~pic+shared arch=linux-ubuntu16.04-x86_64

==> Error: Operation aborted

Note

We try to check for errors upfront!
In Spack we check for errors upfront whenever possible, so don’t worry about your module files: as a name clash was detected nothing has been changed on disk.

The problem here is that without the hashes the four different flavors of netlib-scalapack map to the same module file name. We can add suffixes to differentiate them:

 modules:
   tcl:
     hash_length: 0
     whitelist:
       -  gcc
     blacklist:
       -  '%gcc@5.4.0'
     all:
       suffixes:
         '^openblas': openblas
         '^netlib-lapack': netlib
       filter:
         environment_blacklist: ['CPATH', 'LIBRARY_PATH']
     netlib-scalapack:
       suffixes:
         '^openmpi': openmpi
         '^mpich': mpich

As you can see it is possible to specify rules that apply only to a restricted set of packages using anonymous specs. Regenerating module files now we obtain:

root@module-file-tutorial:/# spack module tcl refresh --delete-tree -y
==> Regenerating tcl module files

root@module-file-tutorial:/# module av

-------------------------------------------- /usr/local/share/spack/modules/linux-ubuntu16.04-x86_64 ---------------------------------------------
   bzip2-1.0.6-gcc-7.2.0     netlib-lapack-3.8.0-gcc-7.2.0                        openmpi-1.10.2-gcc-7.2.0              python-2.7.15-gcc-7.2.0
   cmake-3.12.0-gcc-7.2.0    netlib-scalapack-2.0.2-gcc-7.2.0-netlib-mpich        openssl-1.0.2o-gcc-7.2.0              readline-7.0-gcc-7.2.0
   gcc-7.2.0-gcc-5.4.0       netlib-scalapack-2.0.2-gcc-7.2.0-netlib-openmpi      pkgconf-1.4.2-gcc-7.2.0               sqlite-3.23.1-gcc-7.2.0
   gdbm-1.14.1-gcc-7.2.0     netlib-scalapack-2.0.2-gcc-7.2.0-openblas-mpich      py-numpy-1.14.3-gcc-7.2.0-openblas    zlib-1.2.11-gcc-7.2.0
   mpich-3.2.1-gcc-7.2.0     netlib-scalapack-2.0.2-gcc-7.2.0-openblas-openmpi    py-scipy-1.1.0-gcc-7.2.0-openblas
   ncurses-6.1-gcc-7.2.0     openblas-0.3.0-gcc-7.2.0                             py-setuptools-39.2.0-gcc-7.2.0

Use "module spider" to find all possible modules.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".

Finally we can set a naming_scheme to prevent users from loading modules that refer to different flavors of the same library/application:

modules:
  tcl:
    hash_length: 0
    naming_scheme: '${PACKAGE}/${VERSION}-${COMPILERNAME}-${COMPILERVER}'
    whitelist:
      -  gcc
    blacklist:
      -  '%gcc@5.4.0'
    all:
      conflict:
        - '${PACKAGE}'
      suffixes:
        '^openblas': openblas
        '^netlib-lapack': netlib
      filter:
        environment_blacklist: ['CPATH', 'LIBRARY_PATH']
    netlib-scalapack:
      suffixes:
        '^openmpi': openmpi
        '^mpich': mpich

The final result should look like:

root@module-file-tutorial:/# spack module tcl refresh --delete-tree -y
==> Regenerating tcl module files
root@module-file-tutorial:/# module av

-------------------------------------------- /usr/local/share/spack/modules/linux-ubuntu16.04-x86_64 ---------------------------------------------
   bzip2/1.0.6-gcc-7.2.0                            netlib-scalapack/2.0.2-gcc-7.2.0-netlib-openmpi          py-scipy/1.1.0-gcc-7.2.0-openblas
   cmake/3.12.0-gcc-7.2.0                           netlib-scalapack/2.0.2-gcc-7.2.0-openblas-mpich          py-setuptools/39.2.0-gcc-7.2.0
   gcc/7.2.0-gcc-5.4.0                              netlib-scalapack/2.0.2-gcc-7.2.0-openblas-openmpi (D)    python/2.7.15-gcc-7.2.0
   gdbm/1.14.1-gcc-7.2.0                            openblas/0.3.0-gcc-7.2.0                                 readline/7.0-gcc-7.2.0
   mpich/3.2.1-gcc-7.2.0                            openmpi/1.10.2-gcc-7.2.0                                 sqlite/3.23.1-gcc-7.2.0
   ncurses/6.1-gcc-7.2.0                            openssl/1.0.2o-gcc-7.2.0                                 zlib/1.2.11-gcc-7.2.0
   netlib-lapack/3.8.0-gcc-7.2.0                    pkgconf/1.4.2-gcc-7.2.0
   netlib-scalapack/2.0.2-gcc-7.2.0-netlib-mpich    py-numpy/1.14.3-gcc-7.2.0-openblas

  Where:
   D:  Default Module

Use "module spider" to find all possible modules.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".

Note

TCL specific directive
The directives naming_scheme and conflict are TCL specific and can’t be used in the lmod section of the configuration file.

Add custom environment modifications

At many sites it is customary to set an environment variable in a package’s module file that points to the folder in which the package is installed. You can achieve this with Spack by adding an environment directive to the configuration file:

modules:
  tcl:
    hash_length: 0
    naming_scheme: '${PACKAGE}/${VERSION}-${COMPILERNAME}-${COMPILERVER}'
    whitelist:
      -  gcc
    blacklist:
      -  '%gcc@5.4.0'
    all:
      conflict:
        - '${PACKAGE}'
      suffixes:
        '^openblas': openblas
        '^netlib-lapack': netlib
      filter:
        environment_blacklist: ['CPATH', 'LIBRARY_PATH']
      environment:
        set:
          '${PACKAGE}_ROOT': '${PREFIX}'
    netlib-scalapack:
      suffixes:
        '^openmpi': openmpi
        '^mpich': mpich

Under the hood Spack uses the format() API to substitute tokens in either environment variable names or values. There are two caveats though:

  • The set of allowed tokens in variable names is restricted to PACKAGE, VERSION, COMPILER, COMPILERNAME, COMPILERVER, ARCHITECTURE
  • Any token expanded in a variable name is made uppercase, but other than that case sensitivity is preserved

Regenerating the module files results in something like:

root@module-file-tutorial:/# spack module tcl refresh -y
==> Regenerating tcl module files

root@module-file-tutorial:/# module show gcc
----------------------------------------------------------------------------------------------------------------------------------------------
   /usr/local/share/spack/modules/linux-ubuntu16.04-x86_64/gcc/7.2.0-gcc-5.4.0:
----------------------------------------------------------------------------------------------------------------------------------------------
whatis("The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Ada, and Go, as well as libraries for these languages. ")
conflict("gcc")
prepend_path("PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin")
prepend_path("MANPATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/share/man")
prepend_path("LD_LIBRARY_PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/lib")
prepend_path("LD_LIBRARY_PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/lib64")
prepend_path("CMAKE_PREFIX_PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/")
setenv("CC","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gcc")
setenv("CXX","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/g++")
setenv("FC","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gfortran")
setenv("F77","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gfortran")
setenv("F90","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gfortran")
setenv("GCC_ROOT","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o")
help([[The GNU Compiler Collection includes front ends for C, C++, Objective-C,
Fortran, Ada, and Go, as well as libraries for these languages.
]])

As you can see, the gcc module has the environment variable GCC_ROOT set.

Sometimes it’s also useful to apply environment modifications selectively and target only certain packages. You can, for instance set the common variables CC, CXX, etc. in the gcc module file and apply other custom modifications to the openmpi modules as follows:

modules:
  tcl:
    hash_length: 0
    naming_scheme: '${PACKAGE}/${VERSION}-${COMPILERNAME}-${COMPILERVER}'
    whitelist:
      - gcc
    blacklist:
      - '%gcc@5.4.0'
    all:
      conflict:
        - '${PACKAGE}'
      suffixes:
        '^openblas': openblas
        '^netlib-lapack': netlib
      filter:
        environment_blacklist: ['CPATH', 'LIBRARY_PATH']
      environment:
        set:
          '${PACKAGE}_ROOT': '${PREFIX}'
    gcc:
      environment:
        set:
          CC: gcc
          CXX: g++
          FC: gfortran
          F90: gfortran
          F77: gfortran
    openmpi:
      environment:
        set:
          SLURM_MPI_TYPE: pmi2
          OMPI_MCA_btl_openib_warn_default_gid_prefix: '0'
    netlib-scalapack:
      suffixes:
        '^openmpi': openmpi
        '^mpich': mpich

This time we will be more selective and regenerate only the gcc and openmpi module files:

root@module-file-tutorial:/#  spack module tcl refresh -y gcc
==> Regenerating tcl module files

root@module-file-tutorial:/# spack module tcl refresh -y openmpi
==> Regenerating tcl module files

root@module-file-tutorial:/# module show gcc
----------------------------------------------------------------------------------------------------------------------------------------------
   /usr/local/share/spack/modules/linux-ubuntu16.04-x86_64/gcc/7.2.0-gcc-5.4.0:
----------------------------------------------------------------------------------------------------------------------------------------------
whatis("The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Ada, and Go, as well as libraries for these languages. ")
conflict("gcc")
prepend_path("PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin")
prepend_path("MANPATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/share/man")
prepend_path("LD_LIBRARY_PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/lib")
prepend_path("LD_LIBRARY_PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/lib64")
prepend_path("CMAKE_PREFIX_PATH","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/")
setenv("CC","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gcc")
setenv("CXX","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/g++")
setenv("FC","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gfortran")
setenv("F77","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gfortran")
setenv("F90","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o/bin/gfortran")
setenv("GCC_ROOT","/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-5.4.0/gcc-7.2.0-wwhgyejrjfjtdsgga3hn246bc64jtr6o")
setenv("CC","gcc")
setenv("CXX","g++")
setenv("FC","gfortran")
setenv("F90","gfortran")
setenv("F77","gfortran")
help([[The GNU Compiler Collection includes front ends for C, C++, Objective-C,
Fortran, Ada, and Go, as well as libraries for these languages.
]])

root@module-file-tutorial:/# module show openmpi
----------------------------------------------------------------------------------------------------------------------------------------------
   /usr/local/share/spack/modules/linux-ubuntu16.04-x86_64/openmpi/1.10.2-gcc-7.2.0:
----------------------------------------------------------------------------------------------------------------------------------------------
whatis("An open source Message Passing Interface implementation. ")
conflict("openmpi")
prepend_path("MANPATH","/usr/share/man")
prepend_path("ACLOCAL_PATH","/usr/share/aclocal")
prepend_path("PKG_CONFIG_PATH","/usr/lib/pkgconfig")
setenv("OPENMPI_ROOT","/usr")
setenv("SLURM_MPI_TYPE","pmi2")
setenv("OMPI_MCA_btl_openib_warn_default_gid_prefix","0")
help([[An open source Message Passing Interface implementation. The Open MPI
Project is an open source Message Passing Interface implementation that
is developed and maintained by a consortium of academic, research, and
industry partners. Open MPI is therefore able to combine the expertise,
technologies, and resources from all across the High Performance
Computing community in order to build the best MPI library available.
Open MPI offers advantages for system and software vendors, application
developers and computer science researchers.
]])

Autoload dependencies

Spack can also generate module files that contain code to load the dependencies automatically. You can, for instance generate python modules that load their dependencies by adding the autoload directive and assigning it the value direct:

modules:
  tcl:
    verbose: True
    hash_length: 0
    naming_scheme: '${PACKAGE}/${VERSION}-${COMPILERNAME}-${COMPILERVER}'
    whitelist:
      - gcc
    blacklist:
      - '%gcc@5.4.0'
    all:
      conflict:
        - '${PACKAGE}'
      suffixes:
        '^openblas': openblas
        '^netlib-lapack': netlib
      filter:
        environment_blacklist: ['CPATH', 'LIBRARY_PATH']
      environment:
        set:
          '${PACKAGE}_ROOT': '${PREFIX}'
    gcc:
      environment:
        set:
          CC: gcc
          CXX: g++
          FC: gfortran
          F90: gfortran
          F77: gfortran
    openmpi:
      environment:
        set:
          SLURM_MPI_TYPE: pmi2
          OMPI_MCA_btl_openib_warn_default_gid_prefix: '0'
    netlib-scalapack:
      suffixes:
        '^openmpi': openmpi
        '^mpich': mpich
    ^python:
      autoload:  'direct'

and regenerating the module files for every package that depends on python:

root@module-file-tutorial:/# spack module tcl refresh -y ^python
==> Regenerating tcl module files

Now the py-scipy module will be:

#%Module1.0
## Module file created by spack (https://github.com/spack/spack) on 2018-07-10 15:15:09.308128
##
## py-scipy@1.1.0%gcc@7.2.0 arch=linux-ubuntu16.04-x86_64 /fdiryak
##


module-whatis "SciPy (pronounced 'Sigh Pie') is a Scientific Library for Python. It provides many user-friendly and efficient numerical routines such as routines for numerical integration and optimization."

proc ModulesHelp { } {
puts stderr "SciPy (pronounced "Sigh Pie") is a Scientific Library for Python. It"
puts stderr "provides many user-friendly and efficient numerical routines such as"
puts stderr "routines for numerical integration and optimization."
}

if ![ is-loaded python/2.7.15-gcc-7.2.0 ] {
    puts stderr "Autoloading python/2.7.15-gcc-7.2.0"
    module load python/2.7.15-gcc-7.2.0
}
if ![ is-loaded openblas/0.3.0-gcc-7.2.0 ] {
    puts stderr "Autoloading openblas/0.3.0-gcc-7.2.0"
    module load openblas/0.3.0-gcc-7.2.0
}
if ![ is-loaded py-numpy/1.14.3-gcc-7.2.0-openblas ] {
    puts stderr "Autoloading py-numpy/1.14.3-gcc-7.2.0-openblas"
    module load py-numpy/1.14.3-gcc-7.2.0-openblas
}
conflict py-scipy

prepend-path LD_LIBRARY_PATH "/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-7.2.0/py-scipy-1.1.0-fdiryak4ywv2krvqo7uzogfcc6uckkdp/lib"
prepend-path CMAKE_PREFIX_PATH "/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-7.2.0/py-scipy-1.1.0-fdiryak4ywv2krvqo7uzogfcc6uckkdp/"
prepend-path PYTHONPATH "/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-7.2.0/py-scipy-1.1.0-fdiryak4ywv2krvqo7uzogfcc6uckkdp/lib/python2.7/site-packages"
setenv PY_SCIPY_ROOT "/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-7.2.0/py-scipy-1.1.0-fdiryak4ywv2krvqo7uzogfcc6uckkdp"

and will contain code to autoload all the dependencies:

root@module-file-tutorial:/# module load py-scipy
Autoloading python/2.7.15-gcc-7.2.0
Autoloading openblas/0.3.0-gcc-7.2.0
Autoloading py-numpy/1.14.3-gcc-7.2.0-openblas

In case messages are unwanted during the autoload procedure, it will be sufficient to omit the line setting verbose: True in the configuration file above.

Hierarchical module files

So far we worked with non-hierarchical module files, i.e. with module files that are all generated in the same root directory and don’t attempt to dynamically modify the MODULEPATH. This results in a flat module structure where all the software is visible at the same time:

root@module-file-tutorial:/# module avail

-------------------------------------------- /usr/local/share/spack/modules/linux-ubuntu16.04-x86_64 ---------------------------------------------
   bzip2/1.0.6-gcc-7.2.0                                    openblas/0.3.0-gcc-7.2.0           (L)
   cmake/3.11.4-gcc-7.2.0                                   openmpi/1.10.2-gcc-7.2.0
   gcc/7.2.0-gcc-5.4.0                                      openssl/1.0.2o-gcc-7.2.0
   gdbm/1.14.1-gcc-7.2.0                                    pkgconf/1.4.2-gcc-7.2.0
   mpich/3.2.1-gcc-7.2.0                                    py-numpy/1.14.3-gcc-7.2.0-openblas (L)
   ncurses/6.1-gcc-7.2.0                                    py-scipy/1.1.0-gcc-7.2.0-openblas  (L)
   netlib-lapack/3.8.0-gcc-7.2.0                            py-setuptools/39.2.0-gcc-7.2.0
   netlib-scalapack/2.0.2-gcc-7.2.0-netlib-mpich            python/2.7.15-gcc-7.2.0            (L)
   netlib-scalapack/2.0.2-gcc-7.2.0-netlib-openmpi          readline/7.0-gcc-7.2.0
   netlib-scalapack/2.0.2-gcc-7.2.0-openblas-mpich          sqlite/3.23.1-gcc-7.2.0
   netlib-scalapack/2.0.2-gcc-7.2.0-openblas-openmpi (D)    zlib/1.2.11-gcc-7.2.0

  Where:
   L:  Module is loaded
   D:  Default Module

Use "module spider" to find all possible modules.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".

This layout is quite simple to deploy, but you can see from the above snippet that nothing prevents users from loading incompatible sets of modules:

root@module-file-tutorial:/# module purge
root@module-file-tutorial:/# module load netlib-lapack/3.8.0-gcc-7.2.0 openblas/0.3.0-gcc-7.2.0
root@module-file-tutorial:/# module list

Currently Loaded Modules:
  1) netlib-lapack/3.8.0-gcc-7.2.0   2) openblas/0.3.0-gcc-7.2.0

Even if conflicts directives are carefully placed in module files, they:

  • won’t enforce a consistent environment, but will just report an error
  • need constant updates, for instance as soon as a new compiler or MPI library is installed

Hierarchical module files try to overcome these shortcomings by showing at start-up only a restricted view of what is available on the system: more specifically only the software that has been installed with OS provided compilers. Among this software there will be other - usually more recent - compilers that, once loaded, will prepend new directories to MODULEPATH unlocking all the software that was compiled with them. This “unlocking” idea can then be extended arbitrarily to virtual dependencies, as we’ll see in the following section.

Core/Compiler/MPI

The most widely used hierarchy is the so called Core/Compiler/MPI where, on top of the compilers, different MPI libraries also unlock software linked to them. There are just a few steps needed to adapt the modules.yaml file we used previously:

  1. enable the lmod file generator
  2. change the tcl tag to lmod
  3. remove tcl specific directives (naming_scheme and conflict)
  4. declare which compilers are considered core_compilers
  5. remove the mpi related suffixes (as they will be substituted by hierarchies)

After these modifications your configuration file should look like:

modules:
  enable::
    - lmod
  lmod:
    core_compilers:
      - 'gcc@5.4.0'
    hierarchy:
      - mpi
    hash_length: 0
    whitelist:
      - gcc
    blacklist:
      - '%gcc@5.4.0'
    all:
      suffixes:
        '^openblas': openblas
        '^netlib-lapack': netlib
      filter:
        environment_blacklist: ['CPATH', 'LIBRARY_PATH']
      environment:
        set:
          '${PACKAGE}_ROOT': '${PREFIX}'
    gcc:
      environment:
        set:
          CC: gcc
          CXX: g++
          FC: gfortran
          F90: gfortran
          F77: gfortran
    openmpi:
      environment:
        set:
          SLURM_MPI_TYPE: pmi2
          OMPI_MCA_btl_openib_warn_default_gid_prefix: '0'

Note

Double colon in configuration files
The double colon after enable is intentional and it serves the purpose of overriding the default list of enabled generators so that only lmod will be active (see Overriding entire sections for more details).

The directive core_compilers accepts a list of compilers. Everything built using these compilers will create a module in the Core part of the hierarchy, which is the entry point for hierarchical module files. It is common practice to put the OS provided compilers in the list and only build common utilities and other compilers with them.

If we now regenerate the module files:

root@module-file-tutorial:/# spack module lmod refresh --delete-tree -y
==> Regenerating lmod module files

and update MODULEPATH to point to the Core:

root@module-file-tutorial:/# module purge
root@module-file-tutorial:/# module unuse /usr/local/share/spack/modules/linux-ubuntu16.04-x86_64
root@module-file-tutorial:/# module use /usr/local/share/spack/lmod/linux-ubuntu16.04-x86_64/Core

asking for the available modules will return:

root@module-file-tutorial:/# module avail

------------------------------------------- /usr/local/share/spack/lmod/linux-ubuntu16.04-x86_64/Core --------------------------------------------
   gcc/7.2.0

Use "module spider" to find all possible modules.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".

Unsurprisingly, the only visible module is gcc. Loading that we’ll unlock the Compiler part of the hierarchy:

root@module-file-tutorial:/# module load gcc
root@module-file-tutorial:/# module avail

----------------------------------------- /usr/local/share/spack/lmod/linux-ubuntu16.04-x86_64/gcc/7.2.0 -----------------------------------------
   bzip2/1.0.6     mpich/3.2.1            openblas/0.3.0    pkgconf/1.4.2               py-setuptools/39.2.0    sqlite/3.23.1
   cmake/3.12.0    ncurses/6.1            openmpi/1.10.2    py-numpy/1.14.3-openblas    python/2.7.15           zlib/1.2.11
   gdbm/1.14.1     netlib-lapack/3.8.0    openssl/1.0.2o    py-scipy/1.1.0-openblas     readline/7.0

------------------------------------------- /usr/local/share/spack/lmod/linux-ubuntu16.04-x86_64/Core --------------------------------------------
   gcc/7.2.0 (L)

  Where:
   L:  Module is loaded

Use "module spider" to find all possible modules.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".

The same holds true also for the MPI part, that you can enable by loading either mpich or openmpi. Let’s start by loading mpich:

root@module-file-tutorial:/# module load mpich
root@module-file-tutorial:/# module avail

------------------------------- /usr/local/share/spack/lmod/linux-ubuntu16.04-x86_64/mpich/3.2.1-vt5xcat/gcc/7.2.0 -------------------------------
   netlib-scalapack/2.0.2-netlib    netlib-scalapack/2.0.2-openblas (D)

----------------------------------------- /usr/local/share/spack/lmod/linux-ubuntu16.04-x86_64/gcc/7.2.0 -----------------------------------------
   bzip2/1.0.6     mpich/3.2.1         (L)    openblas/0.3.0    pkgconf/1.4.2               py-setuptools/39.2.0    sqlite/3.23.1
   cmake/3.12.0    ncurses/6.1                openmpi/1.10.2    py-numpy/1.14.3-openblas    python/2.7.15           zlib/1.2.11
   gdbm/1.14.1     netlib-lapack/3.8.0        openssl/1.0.2o    py-scipy/1.1.0-openblas     readline/7.0

------------------------------------------- /usr/local/share/spack/lmod/linux-ubuntu16.04-x86_64/Core --------------------------------------------
   gcc/7.2.0 (L)

  Where:
   L:  Module is loaded
   D:  Default Module

Use "module spider" to find all possible modules.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".


root@module-file-tutorial:/# module load openblas netlib-scalapack/2.0.2-openblas
root@module-file-tutorial:/# module list

Currently Loaded Modules:
  1) gcc/7.2.0   2) mpich/3.2.1   3) openblas/0.3.0   4) netlib-scalapack/2.0.2-openblas

At this point we can showcase the improved consistency that a hierarchical layout provides over a non-hierarchical one:

root@module-file-tutorial:/# module load openmpi

Lmod is automatically replacing "mpich/3.2.1" with "openmpi/1.10.2".

Due to MODULEPATH changes, the following have been reloaded:
  1) netlib-scalapack/2.0.2-openblas

Lmod took care of swapping the MPI provider for us, and it also substituted the netlib-scalapack module to conform to the change in the MPI. In this way we can’t accidentally pull-in two different MPI providers at the same time or load a module file for a package linked to openmpi when mpich is also loaded. Consistency for compilers and MPI is ensured by the tool.

Add LAPACK to the hierarchy

The hierarchy just shown is already a great improvement over non-hierarchical layouts, but it still has an asymmetry: LAPACK providers cover the same semantic role as MPI providers, but yet they are not part of the hierarchy.

To be more practical, this means that although we have gained an improved consistency in our environment when it comes to MPI, we still have the same problems as we had before for LAPACK implementations:

root@module-file-tutorial:/# module list

Currently Loaded Modules:
  1) gcc/7.2.0   2) openblas/0.3.0   3) openmpi/1.10.2   4) netlib-scalapack/2.0.2-openblas

root@module-file-tutorial:/# module load netlib-scalapack/2.0.2-netlib

The following have been reloaded with a version change:
  1) netlib-scalapack/2.0.2-openblas => netlib-scalapack/2.0.2-netlib

root@module-file-tutorial:/# module list

Currently Loaded Modules:
  1) gcc/7.2.0   2) openblas/0.3.0   3) openmpi/1.10.2   4) netlib-scalapack/2.0.2-netlib

Hierarchies that are deeper than Core/Compiler/MPI are probably still considered “unusual” or “impractical” at many sites, mainly because module files are written manually and keeping track of the combinations among multiple providers quickly becomes quite involved.

For instance, having both MPI and LAPACK in the hierarchy means we must classify software into one of four categories:

  1. Software that doesn’t depend on MPI or LAPACK
  2. Software that depends only on MPI
  3. Software that depends only on LAPACK
  4. Software that depends on both

to decide when to show it to the user. The situation becomes more involved as the number of virtual dependencies in the hierarchy increases.

We can take advantage of the DAG that Spack maintains for the installed software and solve this combinatorial problem in a clean and automated way. In some sense Spack’s ability to manage this combinatorial complexity makes deeper hierarchies feasible.

Coming back to our example, let’s add lapack to the hierarchy and remove any remaining suffix:

modules:
  enable::
    - lmod
  lmod:
    core_compilers:
      - 'gcc@5.4.0'
    hierarchy:
      - mpi
      - lapack
    hash_length: 0
    whitelist:
      - gcc
    blacklist:
      - '%gcc@5.4.0'
    all:
      filter:
        environment_blacklist: ['CPATH', 'LIBRARY_PATH']
      environment:
        set:
          '${PACKAGE}_ROOT': '${PREFIX}'
    gcc:
      environment:
        set:
          CC: gcc
          CXX: g++
          FC: gfortran
          F90: gfortran
          F77: gfortran
    openmpi:
      environment:
        set:
          SLURM_MPI_TYPE: pmi2
          OMPI_MCA_btl_openib_warn_default_gid_prefix: '0'

After module files have been regenerated as usual:

root@module-file-tutorial:/# module purge

root@module-file-tutorial:/# spack module lmod refresh --delete-tree -y
==> Regenerating lmod module files

we can see that now we have additional components in the hierarchy:

root@module-file-tutorial:/# module load gcc
root@module-file-tutorial:/# module load openblas
root@module-file-tutorial:/# module avail

----------------------------- /usr/local/share/spack/lmod/linux-ubuntu16.04-x86_64/openblas/0.3.0-pdatzbi/gcc/7.2.0 ------------------------------
   py-numpy/1.14.3    py-scipy/1.1.0

----------------------------------------- /usr/local/share/spack/lmod/linux-ubuntu16.04-x86_64/gcc/7.2.0 -----------------------------------------
   bzip2/1.0.6     mpich/3.2.1            openblas/0.3.0 (L)    pkgconf/1.4.2           readline/7.0
   cmake/3.12.0    ncurses/6.1            openmpi/1.10.2        py-setuptools/39.2.0    sqlite/3.23.1
   gdbm/1.14.1     netlib-lapack/3.8.0    openssl/1.0.2o        python/2.7.15           zlib/1.2.11

------------------------------------------- /usr/local/share/spack/lmod/linux-ubuntu16.04-x86_64/Core --------------------------------------------
   gcc/7.2.0 (L)

  Where:
   L:  Module is loaded

Use "module spider" to find all possible modules.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".


root@module-file-tutorial:/# module load openmpi
root@module-file-tutorial:/# module avail

------------------ /usr/local/share/spack/lmod/linux-ubuntu16.04-x86_64/openmpi/1.10.2-6oewzwj/openblas/0.3.0-pdatzbi/gcc/7.2.0 ------------------
   netlib-scalapack/2.0.2

----------------------------- /usr/local/share/spack/lmod/linux-ubuntu16.04-x86_64/openblas/0.3.0-pdatzbi/gcc/7.2.0 ------------------------------
   py-numpy/1.14.3    py-scipy/1.1.0

----------------------------------------- /usr/local/share/spack/lmod/linux-ubuntu16.04-x86_64/gcc/7.2.0 -----------------------------------------
   bzip2/1.0.6     mpich/3.2.1            openblas/0.3.0 (L)    pkgconf/1.4.2           readline/7.0
   cmake/3.11.4    ncurses/6.1            openmpi/1.10.2 (L)    py-setuptools/39.2.0    sqlite/3.23.1
   gdbm/1.14.1     netlib-lapack/3.8.0    openssl/1.0.2o        python/2.7.15           zlib/1.2.11

------------------------------------------- /usr/local/share/spack/lmod/linux-ubuntu16.04-x86_64/Core --------------------------------------------
   gcc/7.2.0 (L)

  Where:
   L:  Module is loaded

Use "module spider" to find all possible modules.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".

Both MPI and LAPACK providers will now benefit from the same safety features:

root@module-file-tutorial:/# module load py-numpy netlib-scalapack
root@module-file-tutorial:/# module load mpich

Lmod is automatically replacing "openmpi/1.10.2" with "mpich/3.2.1".


Due to MODULEPATH changes, the following have been reloaded:
  1) netlib-scalapack/2.0.2

root@module-file-tutorial:/# module load mpich

Lmod is automatically replacing "openmpi/1.10.2" with "mpich/3.2.1".


Due to MODULEPATH changes, the following have been reloaded:
  1) netlib-scalapack/2.0.2

Because we only compiled py-numpy with openblas the module is made inactive when we switch the LAPACK provider. The user environment is now consistent by design!

Working with templates

As briefly mentioned in the introduction, Spack uses Jinja2 to generate each individual module file. This means that you have all of its flexibility and power when it comes to customizing what gets generated!

Module file templates

The templates that Spack uses to generate module files are stored in the share/spack/templates/module directory within the Spack prefix, and they all share the same common structure. Usually, they start with a header that identifies the type of module being generated. In the case of hierarchical module files it’s:

-- -*- lua -*-
-- Module file created by spack (https://github.com/spack/spack) on {{ timestamp }}
--
-- {{ spec.short_spec }}
--

The statements within double curly brackets {{ ... }} denote expressions that will be evaluated and substituted at module generation time. The rest of the file is then divided into blocks that can be overridden or extended by users, if need be. Control structures , delimited by {% ... %}, are also permitted in the template language:

{% block environment %}
{% for command_name, cmd in environment_modifications %}
{% if command_name == 'PrependPath' %}
prepend_path("{{ cmd.name }}", "{{ cmd.value }}", "{{ cmd.separator }}")
{% elif command_name == 'AppendPath' %}
append_path("{{ cmd.name }}", "{{ cmd.value }}", "{{ cmd.separator }}")
{% elif command_name == 'RemovePath' %}
remove_path("{{ cmd.name }}", "{{ cmd.value }}", "{{ cmd.separator }}")
{% elif command_name == 'SetEnv' %}
setenv("{{ cmd.name }}", "{{ cmd.value }}")
{% elif command_name == 'UnsetEnv' %}
unsetenv("{{ cmd.name }}")
{% endif %}
{% endfor %}
{% endblock %}

The locations where Spack looks for templates are specified in config.yaml:

  # Locations where templates should be found
  template_dirs:
    - $spack/share/spack/templates

and can be extended by users to employ custom templates, as we’ll see next.

Extend the default templates

Let’s assume one of our software is protected by group membership: allowed users belong to the same linux group, and access is granted at group level. Wouldn’t it be nice if people that are not yet entitled to use it could receive a helpful message at module load time that tells them who to contact in your organization to be inserted in the group?

To automate the generation of module files with such site-specific behavior we’ll start by extending the list of locations where Spack looks for module files. Let’s create the file ~/.spack/config.yaml with the content:

config:
  template_dirs:
    - $HOME/.spack/templates

This tells Spack to also search another location when looking for template files. Next, we need to create our custom template extension in the folder listed above:

{% extends "modules/modulefile.lua" %}
{% block footer %}
-- Access is granted only to specific groups
if not isDir("{{ spec.prefix }}") then
    LmodError (
        "You don't have the necessary rights to run \"{{ spec.name }}\".\n\n",
        "\tPlease write an e-mail to 1234@foo.com if you need further information on how to get access to it.\n"
    )
end
{% endblock %}

Let’s name this file group-restricted.lua. The line:

{% extends "modules/modulefile.lua" %}

tells Jinja2 that we are reusing the standard template for hierarchical module files. The section:

{% block footer %}
-- Access is granted only to specific groups
if not isDir("{{ spec.prefix }}") then
    LmodError (
        "You don't have the necessary rights to run \"{{ spec.name }}\".\n\n",
        "\tPlease write an e-mail to 1234@foo.com if you need further information on how to get access to it.\n"
    )
end
{% endblock %}

overrides the footer block. Finally, we need to add a couple of lines in modules.yaml to tell Spack which specs need to use the new custom template. For the sake of illustration let’s assume it’s netlib-scalapack:

modules:
  enable::
    - lmod
  lmod:
    core_compilers:
      - 'gcc@5.4.0'
    hierarchy:
      - mpi
      - lapack
    hash_length: 0
    whitelist:
      - gcc
    blacklist:
      - '%gcc@5.4.0'
      - readline
    all:
      filter:
        environment_blacklist: ['CPATH', 'LIBRARY_PATH']
      environment:
        set:
          '${PACKAGE}_ROOT': '${PREFIX}'
    gcc:
      environment:
        set:
          CC: gcc
          CXX: g++
          FC: gfortran
          F90: gfortran
          F77: gfortran
    openmpi:
      environment:
        set:
          SLURM_MPI_TYPE: pmi2
          OMPI_MCA_btl_openib_warn_default_gid_prefix: '0'
    netlib-scalapack:
      template: 'group-restricted.lua'

If we regenerate the module files one last time:

root@module-file-tutorial:/# spack module lmod refresh -y netlib-scalapack
==> Regenerating lmod module files

we’ll find the following at the end of each netlib-scalapack module file:

-- Access is granted only to specific groups
if not isDir("/usr/local/opt/spack/linux-ubuntu16.04-x86_64/gcc-7.2.0/netlib-scalapack-2.0.2-d3lertflood3twaor44eam2kcr4l72ag") then
    LmodError (
        "You don't have the necessary rights to run \"netlib-scalapack\".\n\n",
        "\tPlease write an e-mail to 1234@foo.com if you need further information on how to get access to it.\n"
    )
end

and every user that doesn’t have access to the software will now be redirected to the right e-mail address where to ask for it!