Skip to content

Instantly share code, notes, and snippets.

@dliptai
Created July 30, 2025 02:58
Show Gist options
  • Select an option

  • Save dliptai/f99cc8e546f6c4f0a0b774ba252058ec to your computer and use it in GitHub Desktop.

Select an option

Save dliptai/f99cc8e546f6c4f0a0b774ba252058ec to your computer and use it in GitHub Desktop.
Lmod hook to show missing dependencies/versions available when module load fails
require("strict")
local hook = require("Hook")
local Cache = require("Cache")
local Dbg = require("Dbg")
local dbg = Dbg:dbg()
------------------------------------------------------------------
-- Helper functions to parse module strings
------------------------------------------------------------------
local function parse_module_string(module_str)
-- Reject invalid strings that start with slash
if string.match(module_str, "^\"/") then
return nil, nil
end
-- Try to parse as "name/version" format
local name, version = string.match(module_str, "\"([^/]*)/(.*)\"")
if name then
return name, version
end
-- Fall back to parsing as "name" only format
name = string.match(module_str, "\"(.*)\"")
return name, nil
end
local function get_available_versions(tbl, module_name)
local versions = {}
local seen = {} -- set-like table to track unique versions
local moduleT = tbl[module_name]
if moduleT then
for _, valueT in pairs(moduleT) do
local version = valueT["Version"]
if not seen[version] then
seen[version] = true
table.insert(versions, version)
end
end
end
table.sort(versions)
return versions
end
local function get_dependencies(tbl, module_name, module_version)
local dependencies = {}
local seen = {} -- set-like table to track unique dependencies
local moduleT = tbl[module_name]
if moduleT then
for key, valueT in pairs(moduleT) do
if valueT["Version"] == module_version then
local parents = valueT["parentAA"]
if parents and #parents == 1 then
local deps = parents[1]
if deps then
local dep_str = table.concat(deps, " ")
-- If dep_str contains gcccore, replace it with gcc
if dep_str:find("gcccore") then
dep_str = dep_str:gsub("gcccore", "gcc")
end
if not seen[dep_str] then
seen[dep_str] = true
table.insert(dependencies, dep_str)
end
end
end
end
end
end
table.sort(dependencies)
return dependencies
end
------------------------------------------------------------------
-- Hook to modify error messages.
-- (This gets called on every message, warnings and errors)
------------------------------------------------------------------
local function errwarnmsg_hook(kind, key, msg, t)
-- kind is either lmoderror, lmodwarning or lmodmessage
-- msg is the actual message to display (as string)
-- key is a unique key for the message (see messageT.lua)
-- t is a table with the keys used in msg
dbg.start{"errwarnmsg_hook"}
dbg.print{"kind: ", kind," key: ",key,"\n"}
dbg.print{"keys: ", t, "\n"}
if kind == "lmoderror" then
-- Build the cache
local cache = Cache:singleton{buildCache=true}
local _, my_dbT, _, _ = cache:build()
local module_name = nil
local module_version = nil
local module_string = nil
if key == "e_Failed_Load_2" then
-- "e_Failed_Load_2" is the case when the module is known, but cannot be loaded.
-- Table keys are:
-- kB: requested module as string without quotes
-- kA: requested module as string with quotes ""
module_string = t.kA
end
if key == "e_Failed_Load" then
-- "e_Failed_Load" is the case when the module is not known.
-- This may be because:
-- 1. The module doesn't exist
-- 2. The module exists, but not the requested version
-- 3. The module is hidden
-- Table keys are:
-- module_list: requested module as string with quotes ""
module_string = t.module_list
end
if module_string then
module_name, module_version = parse_module_string(module_string)
dbg.print{"Parsed module name: ", module_name, " version: ", module_version, "\n"}
-- If module_name is nil, do nothing
-- If module name is not nil, but module_version is nil, find all the versions of the module in the database
-- If both are not nil, find the specific module in the database and all its dependencies
if module_name then
if not module_version then
-- Find all versions of the module
local versions = get_available_versions(my_dbT, module_name)
-- if versions is not empty, build the message, otherwise show unknown module
if versions and #versions > 0 then
local versions_str = " - " .. module_name .. "/" .. table.concat(versions, "\n - " .. module_name .. "/")
msg = string.format(msg_no_version, module_name, versions_str)
else
msg = string.format(msg_unknown_module, module_string, module_string)
end
else
-- Find the specific module and its dependencies
local dependencies = get_dependencies(my_dbT, module_name, module_version)
local dependencies_str = " - " .. table.concat(dependencies, "\n - ")
msg = string.format(msg_missing_dependencies, module_name, dependencies_str)
end
end
end
end
dbg.fini()
return msg
end
hook.register("errWarnMsgHook", errwarnmsg_hook)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment