Modules as a convenient way of choosing build chain on macOS

Modules (Environment Modules or Lmod: A New Environment Module System) are the convenient way of setting up your – typically CLI based – environment.

You can efficiently switch back and forth between different tool chains. You can run different tools inside different terminal windows (you probably know the pain of setting up Java inside macOS – when you have more than one JDK installed).

Modules can be easily installed on macOS. All you have to do is following.

——————

1. Building Modules

First of all, you have to get the source code, and install it.

> mkdir -p ~/opt/usr/local
> mkdir -p ~/opt/src
> cd ~/opt/src
> curl -L -O https://netcologne.dl.sourceforge.net/project/modules/Modules/modules-4.4.1/modules-4.4.1.tar.gz
> tar zxf modules-4.4.1.tar.gz
> cd modules-4.4.1
> ./configure --prefix=$HOME/opt/usr/local/modules/modules-4.4.1
> make
> make install

Modules are there. You can now test them

> module avail
---- .../opt/usr/local/modules/modules-4.4.1/modulefiles ----
dot  module-git  module-info  modules  null  use.own
> module display dot
-------------------------------------------------------------
.../opt/usr/local/modules/modules-4.4.1/modulefiles/dot:

module-whatis   {adds `.' to your PATH environment variable}
append-path     PATH .
-------------------------------------------------------------
> env | grep PATH
PATH=...:/opt/X11/bin
> module load dot
> env | grep PATH
PATH=...:/opt/X11/bin:.

As you can see, this one is supper simple. However, they can serve you better.

——————

2. Handling complex build chains

If you have tried to install gcc-8.3.0 and gcc-9.2.0 at the same time, you probably know the pain of switching back and forth. With modules, it’s very convenient.

Let’s say you have following layout of toolchains

$HOME/opt/usr/local
|-- OpenCoarrays
|   |-- OpenCoarrays-2.8.0-mpich-3.3.1-gcc-8.3.0
|   |-- OpenCoarrays-2.8.0-mpich-3.3.1-gcc-9.2.0
|   |-- OpenCoarrays-2.8.0-openmpi-4.0.2-gcc-8.3.0
|   `-- OpenCoarrays-2.8.0-openmpi-4.0.2-gcc-9.2.0
|-- cmake
|   `-- cmake-3.16.1
|-- etc
|   `-- modulefiles
|-- gcc
|   |-- gcc-8.3.0
|   `-- gcc-9.2.0
|-- gcc_system_root
|   |-- Library -> /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/Library
|   |-- System -> /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System
|   `-- usr
|-- modules
|   `-- modules-4.4.1
|-- mpich
|   |-- mpich-3.3.1-gcc-8.3.0
|   `-- mpich-3.3.1-gcc-9.2.0
`-- openmpi
    |-- openmpi-4.0.2-gcc-8.3.0
    `-- openmpi-4.0.2-gcc-9.2.0

You will end up with this kind of layout once you follow my steps related to installation of:
gcc-8.3.0,
gcc-9.2.0.

Anyways, let’s now try to simplify the process of switching back and forth between these toolchains.

——————

3. Setting up modules

First of all, we need to prepare modules. We can do that quite easily by creating new directory inside $HOME/opt/usr/local/etc/modulefiles.

We have to create new directory: gnuchain. After this is done, your layout should look like this

$HOME/opt/usr/local
`-- etc
    `-- modulefiles
        `-- gnuchain

inside gnuchain directory we need file .module.

#%Module #### -*- mode: Tcl;-*-
##
##      author: mko
##

proc get_gcc_version {} {
    upvar 1 ModulesCurrentModulefile ModulesCurrentModulefile
    set array [split $ModulesCurrentModulefile -]
    set idx [lindex $array end]
    return lindex $array $idx
}

proc get_version {} {
    upvar 1 ModulesCurrentModulefile ModulesCurrentModulefile
    return [file tail $ModulesCurrentModulefile]
}

proc get_mpi {} {
    if {[string first "openmpi" [module-info version]] != -1} {
      return "openmpi"
    } else {
      return "mpich"
    }
}

module-whatis "gnuchain"

conflict gnuchain

set GCC_VERSION [get_gcc_version]
set FULL_VERSION [get_version]
set MPI_VERSION [get_mpi]

prepend-path PATH $env(HOME)/opt/usr/local/gcc/gcc-$GCC_VERSION/bin
prepend-path PATH $env(HOME)/opt/usr/local/$MPI_VERSION/$FULL_VERSION/bin
prepend-path PATH $env(HOME)/opt/usr/local/OpenCoarrays/OpenCoarrays-2.8.0-$FULL_VERSION/bin

this file is responsible for setting up environment such way we get access to all the tools that match our choice.

Once we have it, we need few more files. First of all we need .version. It will point to default version of our module.

#%Module################################
# Default version
set ModulesVersion mpich-3.3.1-gcc-8.3.0

we also need few symbolic links. Each of symbolic links points to version we want to provide via the module system. In this case we need: mpich-3.3.1-gcc-8.3.0, mpich-3.3.1-gcc-9.2.0, openmpi-4.0.2-gcc-8.3.0, openmpi-4.0.2-gcc-9.2.0. These are symbolic links that are created following way:

> ln -s .module mpich-3.3.1-gcc-8.3.0
> ln -s .module mpich-3.3.1-gcc-9.2.0
> ln -s .module openmpi-4.0.2-gcc-8.3.0
> ln -s .module openmpi-4.0.2-gcc-9.2.0

Eventually, your gnuchain directory should look like this

gnuchain
|-- .module
|-- .version
|-- mpich-3.3.1-gcc-8.3.0
|-- mpich-3.3.1-gcc-9.2.0
|-- openmpi-4.0.2-gcc-8.3.0
`-- openmpi-4.0.2-gcc-9.2.0

——————

4. Making sure that modules are properly set

Once we have everything in place, we can add modules to ~/.zshrc.

...
...
source $HOME/opt/usr/local/modules/modules-4.4.1/init/zsh
module use $HOME/opt/usr/local/etc/modulefiles
...
...

——————

5. Working with modules

Now, let’s test the modules. First of all, let’s take a look at what we have

> module avail
---- .../opt/usr/local/etc/modulefiles ----
gnuchain/mpich-3.3.1-gcc-8.3.0(default)
gnuchain/mpich-3.3.1-gcc-9.2.0
gnuchain/openmpi-4.0.2-gcc-8.3.0
gnuchain/openmpi-4.0.2-gcc-9.2.0

We can load one of the modules

> module load gnuchain/mpich-3.3.1-gcc-8.3.0
> mpirun --version
HYDRA build details:
    Version:                                 3.3.1
...
...

Now, let’s switch to other one

> module switch gnuchain/openmpi-4.0.2-gcc-8.3.0
> mpirun --version
mpirun (Open MPI) 4.0.2
...
...

and, eventually, let us try unloading module completely

> module unload gnuchain
> mpirun -version
zsh: command not found: mpirun

That’s it! :)