Modulefile Examples from simple to complex ========================================== Most of the time a modulefile is just a collection of setting environment variables and prepending to PATH or other path like variables. However, modulefiles are actually programs so you can do a great deal if necessary. Here we show some of the techniques that site's or user's might use in their modulefiles. In addition, there are many examples in the Lmod source tree. The ``rt`` directory contains the regression testing suite. Each subdirectory in ``rt`` is a separate test and below that there will be many modulefiles. For example see all the modulefile associated with the load test can be found in ``rt/load/mf``. Some simple modulefiles ~~~~~~~~~~~~~~~~~~~~~~~ An application modulefile might add to ``$PATH``, set a few other environment variables and provide help message as well as ``whatis()`` strings. For example the valgrind memory usage tester might look like:: help([[ To use the valgrind utility on an executable called a.out: valgrind ./a.out ]]) local version = "3.7.0" local base = pathJoin("/apps/valgrind",version) prepend_path("PATH", pathJoin(base,"bin")) -- /app/valgrind/3.7.0/bin setenv( "SITE_VALGRIND_DIR", base) setenv( "SITE_VALGRIND_INC", pathJoin(base,"include")) setenv( "SITE_VALGRIND_LIB", pathJoin(base,"lib")) whatis("Name: ".. pkgName) whatis("Version: " .. fullVersion) whatis("Category: tools") whatis("URL: http://www.valgrind.org") whatis("Description: memory usage tester") A library module might look like:: help([[ ... ]]) whatis("Name: boost") whatis("Version: 1.64") whatis("Category: Lmod/Modulefiles") whatis("Keywords: System, Library, C++") whatis("URL: http://www.boost.org") whatis("Description: Boost provides free peer-reviewed portable C++ source libraries.") setenv("SITE_BOOST_DIR","/apps/intel17/boost/1.64") setenv("SITE_BOOST_LIB","/apps/intel17/boost/1.64/lib") setenv("SITE_BOOST_INC","/apps/intel17/boost/1.64/include") setenv("SITE_BOOST_BIN","/apps/intel17/boost/1.64/bin") setenv("BOOST_ROOT","/apps/intel17/boost/1.64") prepend_path("LD_LIBRARY_PATH","/apps/intel17/boost/1.64/lib") prepend_path("PATH","/apps/intel17/boost/1.64/bin") .. _generic_modules-label: Introspection ~~~~~~~~~~~~~ Lmod provides inspection functions that describe the name and version of a modulefile as well as the path to the modulefile. These functions provide a way to write "generic" modulefiles, i.e. modulefiles that can fill in its values based on the location of the file itself. These ideas work best in the software hierarchy style of modulefiles. For example: suppose the following is a modulefile for Git. Its modulefile is located in the "/apps/mfiles/Core/git" directory and software is installed in "/apps/git/". The following modulefile would work for every version of git:: local pkg = pathJoin("/apps",myModuleName(),myModuleVersion()) local bin = pathJoin(pkg,"bin")) prepend_path("PATH",bin) whatis("Name: ", myModuleName()) whatis("Version: ", myModuleVersion()) whatis("Description: ", "Git is a fast distributive version control system") The contents of this modulefile can be used for multiple versions of the git software, because the local variable bin changes the location of the bin directory to match the version of the used as the name of the file. So if the module file is in `/apps/mfiles/Core/git/2.3.4.lua` then the local variable `bin` will be `/apps/git/2.3.4`. Relative Paths ~~~~~~~~~~~~~~ Suppose you are interested in modules where the module and application location are relative. Suppose that you have an $APPS directory, and below that you have modulefiles and packages, and you would like the modulefiles to find the absolute path of the package location. This can be done with the ``myFileName()`` function and some lua code:: local fn = myFileName() -- 1 local full = myModuleFullName() -- 2 local loc = fn:find(full,1,true)-2 -- 3 local mdir = fn:sub(1,loc) -- 4 local appsDir = mdir:gsub("(.*)/","%1") -- 5 local pkg = pathJoin(appsDir, full) -- 6 To make this example concrete, let's assume that applications are in ``/home/user/apps`` and the modulefiles are in ``/home/user/apps/mfiles``. So if the modulefile is located at ``/home/user/apps/mfiles/git/1.2.lua``, then that is the value of ``fn`` at line 1. The ``full`` variable at line 2 will have ``git/1.2``. What we want is to remove the name of the modulefile and find its parent directory. So we use Lua string member function on ``fn`` to find where ``full`` starts. In most cases ``fn:find(full)`` would work to find where the "git" starts in ``fn`` The trouble is that the Lua find function is expecting a regular expression and in particular ``.`` and ``-`` are regular expression characters. So here we are using ``fn:find(full,1,true)`` to tell Lua to treat each character as is with no special meaning. Line 3 also subtracts 2. The find command reports the location of the start of the string where the "g" in "git" is, We want the value of ``mdir`` to be ``/home/user/apps/mfiles`` so we need to subtract 2. This makes ``mdir`` have the right value. One note is that Lua is a one based language, so locations in strings start at one. It was important for the value of ``mdir`` to remove the trailing ``/`` so that line 5 will do its magic. We want the parent directory of ``mdir``, so the regular expressions says greedily grab every character until the trailing ``/`` and the ``%1`` says to capture the string found in and use that to set ``appsdir`` to ``/home/user/apps``. Finally we wish to set ``pkg`` to the location of the actual application so we combine the value of ``appsdir`` and ``full`` to set ``pkg`` to ``/home/user/apps/git/1.2``. The nice thing about this Lua code is that it figures out the location of the package no matter where it is, as long as the relation between apps directories and modulefiles is consistent. Creating modules like this can be complicated. See :ref:`debugging_modulefiles-label` for helpful tips. Generic Modules with the Hierarchy ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This works great for Core modules. It is a little more complicated for Compiler or MPI/Compiler dependent modules but quite useful. For a concrete example, lets cover how to handle the boost C++ library. This is obviously a compiler dependent module. Suppose you have the gnu compiler collection (gcc) and the intel compiler collection (intel), which means that you'll have a gcc version and an intel version for each version of booth. In order to have generic modules for compiler dependent modules, there must be some conventions to make this work. A suggested way to do this is the following: #. Core modules are placed in `/apps/mfiles/Core`. These are the compilers, programs like git and so on. #. Core software goes in `/apps//`. So git version 2.3.4 goes in `/apps/git/2.3.4` #. Compiler-dependent modulefiles go in `/apps/mfiles/Compiler////` using the **two-digit** rule (discussed below). So the Boost 1.55.0 modulefile built with gcc/4.8.3 would be found in `/apps/mfiles/Compiler/gcc/4.8/boost/1.55.0.lua` #. Compiler-dependent packages go in `/apps///`. So the same Boost 1.55.0 package built with gcc 4.8.3 would be placed in `/apps/gcc-4_8/boost/1.55.0` The above convention depends on the **two-digit** rule. For compilers and mpi stack, we are making the assumption that compiler dependent libraries built with gcc 4.8.1 can be used with gcc 4.8.3. This is not always safe but it works well enough in practice. The above convention also assumes that the boost 1.55.0 package will be placed in `/apps/gcc-4_8/boost/1.55.0`. It couldn't go in */apps/gcc/4.8/...* because that is where the gcc 4.8 package would be placed and it is not a good idea to co-mingle two different packages in the same tree. Another possible choice would be */apps/gcc-4.8/boost/1.55.0*. It is my view that it looks too much like the gcc version 4.8 package location where as *gcc-4_8* doesn't. With all of the above assumptions, we can now create a generic module file for compiler dependent modules such as Boost. In order to make this work, we will need to use the `hierarchyA` function. This function parses the path of the modulefile to return the pieces we need to create a generic boost modulefile:: hierA = hierarchyA(myModuleFullName(),1) The `myModuleFullName()` function returns the full name of the module. So if the module is named **boost/1.55.0**, then that is what it will return. If your site uses module names like `lib/boost/1.55.0` then it will return that correctly as well. The *1* tells Lmod to return just one component from the path. So if the modulefile is located at `/apps/mfiles/Compiler/gcc/4.8/boost/1.55.0.lua`, then `myModuleFullName()` returns **boost/1.55.0** and the `hierarchyA` function returns an array with 1 entry. In this case it returns:: { "gcc/4.8" } The rest of the module file then can make use to this result to form the paths:: local pkgName = myModuleName() local fullVersion = myModuleVersion() local hierA = hierarchyA(myModuleFullName(),1) local compilerD = hierA[1]:gsub("/","-"):gsub("%.","_") local base = pathJoin("/apps",compilerD,pkgName,fullVersion) whatis("Name: "..pkgName) whatis("Version "..fullVersion) whatis("Category: library") whatis("Description: Boost provides free peer-reviewed ".. " portable C++ source libraries.") whatis("URL: http://www.boost.org") whatis("Keyword: library, c++") setenv("TACC_BOOST_LIB", pathJoin(base,"lib")) setenv("TACC_BOOST_INC", pathJoin(base,"include")) The important trick is the building of the `compilerD` variable. It converts the `gcc/4.8` into `gcc-4_8`. This makes the `base` variable be: `/apps/gcc-4_8/boost/1.55.0`. Creating modules like this can be complicated. See :ref:`debugging_modulefiles-label` for helpful tips. A proposed directory structure of /apps/mfiles/Compiler would be:: .base/ gcc/ intel/ .base/ boost/generic.lua gcc/4.8/boost/ 1.55.0.lua -> ../../../.base/boost/generic.lua intel/15.0.2/boost/ 1.55.0.lua -> ../../../.base/boost/generic.lua In this way the `.base/boost/generic.lua` file will be the source file for all the boost version build with gcc and intel compilers. The same technique can be applied for modulefiles for Compiler/MPI dependent packages. In this case, we will create the phdf5 modulefile. This is a parallel I/O package that allows for Hierarchical output. The modulefile is:: local pkgName = myModuleName() local pkgVersion = myModuleVersion() local pkgNameVer = myModuleFullName() local hierA = hierarchyA(pkgNameVer,2) local mpiD = hierA[1]:gsub("/","-"):gsub("%.","_") local compilerD = hierA[2]:gsub("/","-"):gsub("%.","_") local base = pathJoin("/apps", compilerD, mpiD, pkgNameVer) setenv( "TACC_HDF5_DIR", base) setenv( "TACC_HDF5_DOC", pathJoin(base,"doc")) setenv( "TACC_HDF5_INC", pathJoin(base,"include")) setenv( "TACC_HDF5_LIB", pathJoin(base,"lib")) setenv( "TACC_HDF5_BIN", pathJoin(base,"bin")) prepend_path("PATH", pathJoin(base,"bin")) prepend_path("LD_LIBRARY_PATH", pathJoin(base,"lib")) whatis("Name: Parallel HDF5") whatis("Version: " .. pkgVersion) whatis("Category: library, mathematics") whatis("URL: http://www.hdfgroup.org/HDF5") whatis("Description: General purpose library and file format for storing scientific data (parallel I/O version)") We use the same tricks as before, It is just that since the module for phdf5 built by gcc/4.8.3 and mpich/3.1.2 will be found at `/apps/mfiles/MPI/gcc/4.8./mpich/3.1/phdf5/1.8.14.lua`. The results of `hierarchyA(pkgNameVer,2)` would be:: { "mpich/3.1", "gcc/4.8" } This is because the `hierarchyA` works back up the path two elements at a time because the full name of this package is also two elements (phdf5/1.8.14). The `base` variable now becomes:: /apps/gcc-4_8/mpich-3_1/phdf5/1.8.14 The last type of modulefile that needs to be discussed is an mpi stack modulefile such as mpich/3.1.2. This modulefile is more complicated because it has to implement the two-digit rule, build the path to the package and build the new entry to the **MODULEPATH**. The modulefile is:: local pkgNameVer = myModuleFullName() local pkgName = myModuleName() local fullVersion = myModuleVersion() local pkgV = fullVersion:match('(%d+%.%d+)%.?') local hierA = hierarchyA(pkgNameVer,1) local compilerV = hierA[1] local compilerD = compilerV:gsub("/","-"):gsub("%.","_") local base = pathJoin("/apps",compilerD,pkgName,fullVersion) local mpath = pathJoin("/apps/mfiles/MPI", compilerV, pkgName, pkgV) prepend_path("MODULEPATH", mpath) setenv( "TACC_MPICH_DIR", base) setenv( "TACC_MPICH_LIB", pathJoin(base,"lib")) setenv( "TACC_MPICH_BIN", pathJoin(base,"bin")) setenv( "TACC_MPICH_INC", pathJoin(base,"include")) whatis("Name: "..pkgName) whatis("Version "..fullVersion) whatis("Category: mpi") whatis("Description: High-Performance Portable MPI") whatis("URL: http://www.mpich.org") The **Two Digit** rule implemented by forming the `pkgV` variable. The `base` and `mpath` are:: base = "/apps/gcc-4_8/mpich-3_1/phdf5/1.8.14" mpath = "/apps/mfiles/MPI/gcc/4.8/mpich/3.1" The *rt* directory contains all the regression test used by Lmod. As such they contain many examples of modulefiles. To complement this description, the *rt/hierarchy/mf* directory from the source tree contains a complete hierarchy.