Dependent Modules

Let’s assume that module “X” depends on module “A”. There are several ways to handle module dependency. Inside the “X” modulefile you could have one of the following choices:

  1. Use depends_on("A","B") or depends_on_any("C","D", ...)

  2. Use prereq("A")

  3. Use load("A")

  4. Use always_load("A")

  5. Use RPATH to make “X” know where the libraries in “A” can be found.

Let’s examine these choices in order. The main issue for each of these choices is what happens when module “X” is unloaded.

depends_on("A")

This choice loads module “A” on the users behalf if it not already loaded. When module “X” is unloaded, module “A” will be unloaded if it is a dependent load. Imagine the following scenario with depends_on("A"):

$ module purge; module load X; module unload X                => unload A
$ module purge; module load A; module load X; module unload X => keep A

Lmod implements reference counting for modules loaded via depends_on() and only depends_on(). So if “X” and “Y” depend on “A” then:

$ module purge; module load X Y; module unload X   => keep A
$ module purge; module load X Y; module unload X Y => unload A

depends_on_any("C","D")

The function depends_on_any("C","D") works similarly to depends_on() except that Lmod picks the first available module listed. It first checks to see if any of the modules are already loaded. If none are already loaded then picks the first one that can be loaded. On unload, Lmod remembers which of the choices it loaded to unload. It is not an error if that module has already been unloaded.

Complex uses of depends_on()

Sites can have complex dependencies that they might wish to express using depends_on(). Let’s assume that module X depends on the openblas package but only when using gcc. A site might try to do the following in the X modulefile:

-- DO NOT DO THIS!!
if (isloaded("gcc")) then
   depends_on("openblas")
end
-- DO NOT DO THIS!!

Let’s also assume that your site is using the hierarchy where X is a compiler dependent module and you wish to use the same modulefile for X for both the gcc and intel compiler modules. The above code in the X modulefile works correctly when loading but will fail when unloading in this common scenario: if a user tries to swap gcc for intel then the openblas module will likely be left loaded or inactive.

To simplify the discussion, let’s have the user start with the following modules loaded:

$ module list

1) gcc/7.1  2) X/1.0  3) openblas/0.2.20

And lets assume that openblas is a Core module and X is a compiler dependent module. Then executing:

$ module swap gcc intel

causes gcc to be unloaded. When gcc is unloaded it removes the path in MODULEPATH to the gcc dependent modules which means that X will be unloaded and marked as inactive. The way that a module is unloaded is that the contents of the module is evaluated and most action requested are reversed. So load statements cause a module to unload and a depends_on() function is told to forgo() the modules. The isloaded() is not reversed. But as you can see since the gcc modulefile is not loaded the if statement then clause is not evaluated. This means that openblas will still be loaded.

In the case where openblas is a compiler-dependent module then it will be unloaded and marked as inactive. Either way this probably not what the site wants to happen. The trouble here is that environment that happens on load is not the case on unload.

There is another way to determine which compiler and/or mpi stack a module is in and that is its filename. This assumes that you have a rational naming convention for module locations. Using a similar technique to the one describe in Introspection. We can determine which compiler is in use. So if the module file is located in /apps/mfiles/Compiler/<compiler>/<compiler-version>/<app-name>/<app-version> then we can do the following and use the hierarchyA() function in the X modulefile:

local hierA = hierarchyA(myModuleFullName(),1)
if (hierA[1]:find("^gcc/")) then
   depends_on("openblas")
end

This will work correctly for both loading and unloading. This, of course, assumes that the location of the X modulefile is something like:

/apps/mfiles/Compiler/gcc/7.1/X/1.0.lua

prereq("A")

This choice is the one you give for sophisticated users. If a user tried to load module “X” without previously loading “A” then the user will get a message telling the user that they must load “A” before loading “X”. This way the dependency is explicitly handled by the user. When the user unloads “X”, module “A” will remain loaded.

load("A")

This choice will always load module “A” on the users behalf. This is true even if “A” is already loaded. When module “X” is unloaded, module “A” will be unloaded as well. This may surprise some users who might want to continue using the “A” package. At least with prereq(), your users won’t be surprised by this. Another way to handle this is the next choice.

always_load("A")

A site can chose to use always_load() instead. This command is a shorthand for:

if (mode() == "load") then
   load("A")
end

The TCL equivalent is:

if { [ module-info mode load ] } {
   module load A
}

This choice will always load module “A” on the users behalf. This is true even if “A” is already loaded. When module “X” is unloaded, module “A” will remain loaded.

Use RPATH

We have switched to using RPATH for library dependencies at TACC. That is when we build package X, we use the RPATH linking option to link libraries in package A as part of the X rpm. This has the disadvantage that if the A package is removed then the X package is broken. This has happened to us occasionally. In general, however, we have found that this has worked well for us.