---
# System prepended metadata

title: modulefile 語法筆記

---

# modulefile 語法筆記
[預定lmod教材](/cHnypcDNQ4WQkQtYfLaW7w)

lmod modulefile command reference
https://lmod.readthedocs.io/en/latest/050_lua_modulefiles.html

```!
0.
local gcc_root = "/opt/gcc/11.4.0"




prepend_path("PATH", pathJoin(root, "bin"))
prepend_path("LD_LIBRARY_PATH", pathJoin(root, "lib"))
prepend_path("LD_LIBRARY_PATH", pathJoin(root, "lib64"))
prepend_path("C_INCLUDE_PATH", pathJoin(root, "include"))
prepend_path("CPLUS_INCLUDE_PATH", pathJoin(root, "include"))
prepend_path("LIBRARY_PATH", pathJoin(root, "lib"))
prepend_path("LIBRARY_PATH", pathJoin(root, "lib64"))
prepend_path("MANPATH", pathJoin(root, "share/man"))
prepend_path("INFOPATH", pathJoin(root, "share/info"))

1.
local name = myModuleName()
local fullVersion = myModuleVersion()
local pkgVersion = fullVersion:match('(%d+%.%d+)%.?')
local pkgNameVer = pathJoin(name,pkgVersion)
local mroot = os.getenv("MODULEPATH_ROOT")

prepend_path("MODULEPATH", pathJoin(mroot, "Compiler", pkgNameVer))

family("Compiler")

2.
if (mode() == "load") then
   io.stderr:write(myModuleFullName() .. " loaded\n")
end

if (mode() == "unload") then
   io.stderr:write(myModuleFullName() .. " unloaded\n")
end

3.
local gcc_link = pathJoin(gcc_root, "bin", "gcc")
local gpp_link = pathJoin(gcc_root, "bin", "g++")
os.execute("sudo ln -sf " .. gcc_link .. " /usr/bin/gcc")
os.execute("sudo ln -sf " .. gpp_link .. " /usr/bin/g++ ")


```
// 註釋
-- 1 --

// 取得環境變量 USER 的值
os.getenv("USER")

// 把值放入變量前，unload後會移除
prepend_path( "PATH", "/opt/apps/ddt/5.0.1/bin")

// 把值放入變量後，unload後會移除
append_path("PATH",  ".")


// help, 提供module help查詢
help(\[\[The TACC modulefile defines ...]])

// whatis，提供module keyword 查詢
whatis("Version: 5.0.1")
whatis("Keywords: System, Utility")
whatis("URL: http://content.allinea.com/downloads/userguide.pdf")
whatis("Description: Parallel, graphical, symbolic debugger")

// load intel 和 mvapich2
load("intel", "mvapich2")

// 和 load 一樣，但出錯(找不到)時，不報錯
try_load("xalt")

// mode() 當前module的狀態，可以是 "load" 或 "unload"

// nil 是一個lua特殊值，代表什麼都沒有
os.getenv("OMP_NUM_THREADS") == nil

// if 語句
if ( 條件 and 條件) then
  行為
end

// 依賴項，當load時，需要依賴項在前或已load，當unload時會連帶把依賴項unload
prereq("gnuplot")

有一段話: It seems better to either use the prereq function shown above or use the always_load function in the octave module.
// 當load a，load b，a是b的依賴項，unload b，此時a會被unload
// 所以只用always_load或preq，別用load()。？我應該會用always_load，看起來方便的多

// 載入gunplot，不會在本module unload後一起unload
always_load("gnuplot")

// 和load, preq, always_load一起使用，代表>=5.0才可接受
atleast("gnuplot","5.0")



## 階層module 設計
mf->{"Compiler", "Core", "MPI"}

"Core"是放置編譯器的地方
"Core"->{gcc->版本.lua, intel->版本.lua, python->版本.lua}

"Compiler"是放置從編譯器編譯成的文件的地方
"Compiler"->{gcc->版本, intel->版本}
gcc->版本->{boost->版本.lua, impi->版本.lua, python->版本.lua}
intel->版本->{impi->版本.lua}

"MPI"是放置依賴於編譯器和MPI的文件的地方
"MPI"->{gcc->版本->impi->版本, intel->版本->impi->版本}
gcc->版本->impi->版本->{petsc->版本.lua}
intel->版本->impi->版本->{petsc->版本.lua}

以上最少得寫7個mf(modulefile)。編譯器3個，boost,impi,python三個, petsc一個，不同版本可以使用符號鍊結到同一個版本.lua，因為gcc和intel在撰寫上沒有區別。

實際版本.lua內容
gcc
local name        = myModuleName()
local fullVersion = myModuleVersion()
local pkgVersion  = fullVersion:match('(%d+%.%d+)%.?')
local pkgNameVer  = pathJoin(name,pkgVersion)
local mroot       = os.getenv("MODULEPATH_ROOT")

prepend_path("MODULEPATH", pathJoin(mroot, "Compiler", pkgNameVer))

family("Compiler")

---
intel
local name        = myModuleName()
local fullVersion = myModuleVersion()
local pkgVersion  = fullVersion:match('(%d+%.%d+)%.?')
local pkgNameVer  = pathJoin(name,pkgVersion)
local mroot       = os.getenv("MODULEPATH_ROOT")

prepend_path("MODULEPATH", pathJoin(mroot, "Compiler", pkgNameVer))

---
python
setenv("MY_VERSION",myModuleVersion())

---
boost
setenv("BOOST_VERSION", myModuleVersion())

---
impi
-- -*- lua -*-

local helpMsg = \[\[
Intel MPI help
]]

local pkgName      = myModuleName()
local fullVersion  = myModuleVersion()
local pkgV         = fullVersion:match('(%d+%.%d+)%.?')
local hierA        = hierarchyA(pathJoin(pkgName,fullVersion),1)
local compilerV    = hierA\[1]
local compilerD    = compilerV:gsub("/","-"):gsub("%.","_")
local base         = pathJoin("/opt/apps",compilerD,pkgName,fullVersion)
local mroot        = os.getenv("MODULEPATH_ROOT")
local mpath        = pathJoin(mroot, "MPI", compilerV, pkgName, pkgV)

whatis("Name: "..pkgName)
whatis("Version "..fullVersion)
whatis("Category: mpi")
whatis("Description: Intel Version of the Message Passing Interface Library")
whatis("Keyword: library, mpi")

help(helpMsg, "Version ",fullVersion)

setenv(      "TACC_IMPI_DIR", base)
setenv(      "TACC_IMPI_LIB", pathJoin(base,"lib"))
setenv(      "TACC_IMPI_BIN", pathJoin(base,"bin"))
setenv(      "TACC_IMPI_INC", pathJoin(base,"include"))
prepend_path("MANPATH",          pathJoin(base,"man"))
prepend_path('MODULEPATH',       mpath)

prepend_path('LD_LIBRARY_PATH', pathJoin(mpihome,"lib"))
prepend_path('LD_LIBRARY_PATH', pathJoin(mpihome,"lib","openmpi"))
setenv(      'MPIHOME',         base)
setenv(      'MPICH_HOME',      base)
family("MPI")

---
python
setenv("COMPILER_DEPENDENT","yes")
setenv("MY_VERSION",myModuleVersion())

---
petsc
沒有東西



## api 列表


## 實做
我需要弄到套件，這些可以放到一個腳本裡面，自動安裝到正確的地方，然後自動創造module file在正確的地方，然後自動撰寫module file.


## Introspection Functions
:::spoiler 完整測試代碼
以 gcc/test.lua 舉例

代碼
io.stderr:write(myModuleName () .. "\n")
io.stderr:write(myModuleVersion () .. "\n")
io.stderr:write(myModuleFullName () .. "\n")
io.stderr:write(myModuleUsrName () .. "\n")
io.stderr:write(myFileName () .. "\n")
io.stderr:write(myShellName () .. "\n")
io.stderr:write(myShellType () .. "\n")
io.stderr:write("\n")

輸出
gcc
test
gcc/test
gcc/test
/opt/apps/modulefiles/Core/gcc/test.lua
bash
sh

對照板
myModuleName () == gcc
myModuleVersion () == test
myModuleFullName () == gcc/test
myModuleUsrName () == gcc/test
myFileName () == /opt/apps/modulefiles/Core/gcc/test.lua
myShellName () == bash
myShellType () == sh
:::

:::spoiler API 說明
The following functions allow for more generic modulefiles by finding the name and version of a modulefile.

myModuleName ():

Returns the name of the current modulefile without the version.

myModuleVersion ():

Returns the version of the current modulefile.

myModuleFullName ():

Returns the name and version of the current modulefile.

myModuleUsrName ():

Returns the name the user specified to load a module. So it could be the name or the name and version.

myFileName ():

Returns the absolute file name of the current modulefile.

myShellName ():

Returns the name of the shell the user specified on the command line.

myShellType ():

Returns the shellType based on the name of the shell the user specified on the command line. It returns sh for sh, bash, zsh, csh for csh, tcsh. Otherwise it is the same as myShellName ().

hierarchyA (“fullName”, level):

Returns the hierarchy of the current module. See the section on Generic Modules for more details.
:::


### lmod 教學草稿
#### 學習前知識







#### lmod 概觀
lmod 是一個通過改變環境變量來選擇使用套件的套件，其核心是環境變量的改變。

lmod並不能安裝套件，只能管理套件。

學習 lmod 分成三件事情，打 module 指令，撰寫 module file 指令 和 階層模組設計。

除此之外，旁枝學習還有，與spack協作。spack 本身有良好的對 lmod 支持，可以一鍵生成 module file。

##### lmod 的歷史淵源
lmod 是 Environment Modules 的繼承者，前者使用 lua，後者使用 tcl，所以也稱為 tcl Module。lmod 兼容 Environment Modules 且更加強大和快速。 
#### module 指令
module 指令的核心是操控 module file。
module 指令是用來`載入module file`/`載出module file`/`查找module file`/`設置MODULEPATH` 的指令。
`MODULEPATH`環境變量 是保存 module file 存在的位置的變量，hierarchy module 會通過 MODULEPATH 實現。

#### module file 指令
module file 是一種用 lua 寫成的腳本文件，目標是改變環境變量，可以被 Lmod 執行，遵循 lmod 設計的語法。
看起來像是 `.../套件名/版本號.lua` ，其中 `版本號.lua` 就是module file。

#### 階層模組設計
這個東西確定了你的module file位置，套件實際位置，目錄設置的方法。
根據 lmod 官方的說法，module file 要設計成 Core / Compiler / MPI。
Core 放置不依賴於編譯器或MPI的，基本是就是編譯器。
Compiler 放置依賴於編譯器但不依賴於MPI的，基本是一般的套件。
MPI 放置依賴於編譯器和MPI的套件。

階層模組設計仰賴於兩個核心的東西。
`MODULEPATH 環境變量`: 此變量是 lmod 查找能 load 的 modulefile 的變量，不在此變量的 modulefile 就不能被查找到。
`family("Compiler") 函數`: 假設在多個 modulefile 中都有 family("Compiler") ，lmod 保證只有一個會被 load，其餘 unload。

如此，當我們 `load gcc/11.4.0` 時，可以設計成把`mf/Compiler/gcc/11.4.0`添加到 `MODULEPATH` 環境變量，如此 `cuda/12.6.77` ，`ffmpeg/7.0.2` 和 `curl/8.10.1` 就會出現在可使用列表。
同時，假設之前是 `load gcc/12.3.0`，此時由於`family("Compiler")`， `gcc/12.3.0`會被 unload，它添加的 `MODULEPATH` 的值`mf/Compiler/gcc/12.3.0`會被移除，如此 lmod 就查找不到 `sqlite/3.46.0`。

module file 設計範例
```
mf
├── Compiler
│   ├── gcc
│   │   ├── 11.4.0
│   │   │   ├── cuda
│   │   │   │   ├── 12.6.77.lua
│   │   │   │   └── 11.4.0.lua
│   │   │   ├── openmpi
│   │   │   │   └── 4.1.5.lua
│   │   │   └── ffmpeg
│   │   │       └── 7.0.2.lua
│   │   └── 12.3.0
│   │       └── openmpi
│   │           ├── 3.0.5.lua
│   │           └── 5.0.2.lua
│   └── icc
├── Core
│   ├── gcc
│   │   ├── 11.4.0.lua
│   │   └── 12.3.0.lua
│   └── icc
│       ├── compilerVersion1.lua
│       └── compilerVersion2.lua
└── MPI
    ├── gcc
    │   └── 12.3.0
    │       ├── openmpi
    │       │   ├── 3.0.5
    │       │   │   ├── petsc
    │       │   │   │   ├── 3.18.1
    │       │   │   │   └── 3.11.0
    │       │   │   └── lammps
    │       │   │       └── 29Aug2024
    │       │   └── 5.0.2
    │       └── MPICH
    └── gcc
        └── 11.4.0
            └── openmpi
                └── 4.1.5
                    └── petsc
                        └── 3.21.5

```
我目前安裝的東西
```
modulefiles/
├── Compiler
│   └── gcc
│       └── 11.4.0
│           ├── cuda
│           │   └── 12.6.77.lua
│           └── doxygen
│               └── 1.12.0.lua
└── Core
    └── gcc
        ├── 11.4.0.lua
        └── 12.3.0.lua
```
套件實際位置





