1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use crate::module::{Dependency, Environment, Module};
pub fn generate(module: &Module) -> String {
// Generate a modulefile with support for flavours
// The modulefile has the following format:
/*
#%Module
# ${Module Name}
# ${Module Description}
# ${Module Metadata}
# MODULEFILE GENERATED BY SCCMOD
# https://github.com/Pencilcaseman/sccmod
package require flavours
flavours init
proc ModulesHelp { } {
puts stderr "
${Module Name}: ${Module Description}
${Module Metadata}
"
}
module-whatis "${Module Description}"
# Dependend module classes -- e.g. MPI requires a Compiler
flavours prereq -class ${Module Dependency: Class}
# Multiple versions of the same module cannot be loaded simultaneously
flavours conflict ${Module Name}
# Root directory of the module, containing all "flavours"
# e.g.: |-> OpenMPI
# |-> gcc-11
# |-> gcc-12
# |-> gcc-13
# |-> clang-17
# |-> clang-18
flavours root ${Module Root Dir}
flavours revision 1
flavours commit
# Set environment variables
setenv ${var} ${value}
# ...
# Modify environment variables
# Directories are relative to the "flavour" root
flavours prepend-path PATH bin
flavours prepend-path LIBRARY_PATH lib
flavours prepend-path LD_LIBRARY_PATH lib
flavours prepend-path CPATH include
# Cleanup and reload conflicting modules
flavours cleanup
*/
let module_name = &module.name;
let mut module_metadata_str = String::new();
for (key, value) in module.metadata.iter() {
module_metadata_str.push_str(&format!("# {key}: {value}\n"));
}
let mut module_metadata_str_no_hashes = String::new();
for (key, value) in module.metadata.iter() {
module_metadata_str_no_hashes.push_str(&format!("{key}: {value}\n"));
}
let no_description_provided = "No description provided".to_string();
let module_description = module
.metadata
.get("description")
.unwrap_or(&no_description_provided);
let mut class_definitions = String::new();
for class in module.dependencies.iter().filter_map(|dep| {
if let Dependency::Class(name) = dep {
Some(name)
} else {
None
}
}) {
class_definitions.push_str(&format!("flavours prereq -class {class}\n"));
}
let root_dir = &module.install_path;
let mut environment_variables = String::new();
for (key, value) in module.environment.iter() {
environment_variables.push_str(&match value {
Environment::Set(val) => format!("setenv \"{key}\" \"{val}\"\n"),
Environment::Append(val) => format!("flavours append-path \"{key}\" \"{val}\"\n"),
Environment::Prepend(val) => format!("flavours prepend-path \"{key}\" \"{val}\"\n"),
})
}
format!(
r#"#%Module
# MODULEFILE GENERATED BY SCCMOD
# https://github.com/Pencilcaseman/sccmod
# Metadata
{module_metadata_str}
package require flavours
flavours init
proc ModulesHelp {{ }} {{
puts stderr "
{module_metadata_str_no_hashes}
"
}}
module-whatis "{module_description}"
{class_definitions}
flavours conflict {module_name}
flavours root {root_dir}
flavours revision 1
flavours commit
{environment_variables}
# Cleanup and reload conflicting modules
flavours cleanup
"#
)
}