Skip to content

Commit 95740f0

Browse files
authored
modules: enforce modules:default:roots:tcl to be unset and arch_folder:false (#276)
This is a minor bug fix, since it does not apply to any recipe currently published in https://github.com/eth-cscs/alps-uenv, but it represents a good sanity check that might help users. Since the default configuration for spack would be to consider `arch_folder: true`, if the recipes don't explicitly set it, the modules get generated with an additional folder level, namely the arch_folder (e.g. `/user-environment/modules/linux-sles15-neoverse_v2` instead of simply `/user-environment/modules`). The problem arises with usage, because `uenv` sets `MODULEPATH` to `/user-environment/modules`, so the modules are not anymore reachable simply by name, but they need to be prefixed with the specific arch_folder (e.g. `linux-sles15-neoverse_v2/cmake` instead of `cmake`). That does not translates just to a burden for the user that has to prefix all module names with the arch_folder, but it actually does not work because modules dependencies in modules themselves are not prefixed with the `arch_folder`. Spack implements this functionality by adding to `MODULEPATH` all arch_folders available on the system. I think we can live without functionality, but we should anyway enforce it to catch earlier (at build time) potential problems (which would otherwise show up just at usage time).
1 parent c912c25 commit 95740f0

File tree

5 files changed

+133
-4
lines changed

5 files changed

+133
-4
lines changed

docs/recipes.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ A recipe is comprised of the following yaml files in a directory:
77
* `compilers.yaml`: the compilers provided by the stack.
88
* `environments.yaml`: environments that contain all the software packages.
99
* `modules.yaml`: _optional_ module generation rules
10-
* follows the spec for [spack module configuration](https://spack.readthedocs.io/en/latest/module_file_support.html)
10+
* follows the spec for [spack module configuration](https://spack.readthedocs.io/en/latest/module_file_support.html#the-modules-yaml-config-file-and-module-sets) with small exceptions (see [Modules](#modules) for more details).
1111
* `packages.yaml`: _optional_ define external packages
1212
* follows the spec for [spack package configuration](https://spack.readthedocs.io/en/latest/build_settings.html)
1313
* `repo`: _optional_ custom spack package definitions.
@@ -445,7 +445,14 @@ The presence of a `modules.yaml` file in the recipe is a necessary and sufficien
445445
!!! warning
446446
`config:modules` field has been deprecated. It can still be specified, but it has to be consistent with the presence of `modules.yaml` file.
447447

448-
Modules are generated for the installed compilers and packages by spack. Rules for module generation in `modules.yaml` file should be provided as per the [spack documentation](https://spack.readthedocs.io/en/latest/module_file_support.html).
448+
Modules are generated for the installed compilers and packages by spack.
449+
450+
!!! info "Rules for module generation in `modules.yaml`"
451+
Stackinator relies on Spack's module generation capabilities, that's the reason why the `modules.yaml` file has to follow [Spack's specification](https://spack.readthedocs.io/en/latest/module_file_support.html#the-modules-yaml-config-file-and-module-sets).
452+
But, **there are some differences**:
453+
454+
- `modules:default:arch_folder` defaults to `false`. If set to `true` an error is raised, as Stackinator does not support this feature;
455+
- `modules:default:roots:tcl` is ignored, as Stackinator automatically configures the module root to be inside the uenv mount point.
449456

450457
## Custom Spack Packages
451458

stackinator/recipe.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,14 @@ def __init__(self, args):
6969
if modules_path.is_file():
7070
with modules_path.open() as fid:
7171
self.modules = yaml.load(fid, Loader=yaml.Loader)
72-
# Note: it should match MODULEPATH set by envvars and used by uenv view "modules"
73-
self.modules["modules"]["default"]["roots"]["tcl"] = (pathlib.Path(self.mount) / "modules").as_posix()
72+
schema.ModulesValidator.validate(self.modules)
73+
74+
# Note:
75+
# modules root should match MODULEPATH set by envvars and used by uenv view "modules"
76+
# so we enforce that the user does not override it in modules.yaml
77+
self.modules["modules"].setdefault("default", {}).setdefault("roots", {}).setdefault(
78+
"tcl", (self.mount / "modules").as_posix()
79+
)
7480

7581
# DEPRECATED field `config:modules`
7682
if "modules" in self.config:

stackinator/schema.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,16 @@ def check_config_version(instance):
108108
raise RuntimeError("incompatible uenv recipe version")
109109

110110

111+
def check_module_paths(instance):
112+
try:
113+
instance["modules"]["default"]["roots"]["tcl"]
114+
root_logger.warning("'modules:default:roots:tcl' field is ignored and overwritten by stackinator.")
115+
except KeyError:
116+
pass
117+
118+
111119
ConfigValidator = SchemaValidator(prefix / "schema/config.json", check_config_version)
112120
CompilersValidator = SchemaValidator(prefix / "schema/compilers.json")
113121
EnvironmentsValidator = SchemaValidator(prefix / "schema/environments.json")
114122
CacheValidator = SchemaValidator(prefix / "schema/cache.json")
123+
ModulesValidator = SchemaValidator(prefix / "schema/modules.json", check_module_paths)

stackinator/schema/modules.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"title": "Schema for Stackinator constraints on Spack Modules modules.yaml",
4+
"type" : "object",
5+
"required": ["modules"],
6+
"properties" : {
7+
"modules" : {
8+
"type": "object",
9+
"properties": {
10+
"default": {
11+
"type": "object",
12+
"default": {"arch_folder": false},
13+
"properties": {
14+
"roots": {
15+
"type": "object",
16+
"additionalProperties": true,
17+
"properties": {
18+
"tcl": {
19+
"type": "string"
20+
}
21+
}
22+
},
23+
"arch_folder": {
24+
"type": "boolean",
25+
"const": false,
26+
"default": false
27+
}
28+
}
29+
}
30+
}
31+
}
32+
}
33+
}
34+

unittests/test_schema.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,76 @@ def test_recipe_environments_yaml(recipe_paths):
199199
with open(p / "environments.yaml") as fid:
200200
raw = yaml.load(fid, Loader=yaml.Loader)
201201
schema.EnvironmentsValidator.validate(raw)
202+
203+
204+
@pytest.mark.parametrize(
205+
"recipe",
206+
[
207+
dedent(
208+
"""
209+
modules: {}
210+
"""
211+
),
212+
dedent(
213+
"""
214+
modules:
215+
default:
216+
arch_folder: false
217+
"""
218+
),
219+
dedent(
220+
"""
221+
modules:
222+
# Paths tomodules: check when creating modules for all module sets
223+
prefix_insmodules:pections:
224+
bin:
225+
- PATH
226+
lib:
227+
- LD_LIBRARY_PATH
228+
lib64:
229+
- LD_LIBRARY_PATH
230+
231+
default:
232+
arch_folder: false
233+
# Where to install modules
234+
tcl:
235+
all:
236+
autoload: none
237+
hash_length: 0
238+
exclude_implicits: true
239+
exclude: []
240+
projections:
241+
all: '{name}/{version}'
242+
"""
243+
),
244+
dedent(
245+
"""
246+
modules:
247+
default:
248+
roots:
249+
tcl: /path/which/is/going/to/be/ignored
250+
"""
251+
),
252+
],
253+
)
254+
def test_valid_modules_yaml(recipe):
255+
instance = yaml.load(recipe, Loader=yaml.Loader)
256+
schema.ModulesValidator.validate(instance)
257+
assert not instance["modules"]["default"]["arch_folder"]
258+
259+
260+
@pytest.mark.parametrize(
261+
"recipe",
262+
[
263+
dedent(
264+
"""
265+
modules:
266+
default:
267+
arch_folder: true
268+
"""
269+
),
270+
],
271+
)
272+
def test_invalid_modules_yaml(recipe):
273+
with pytest.raises(Exception):
274+
schema.ModulesValidator.validate(yaml.load(recipe, Loader=yaml.Loader))

0 commit comments

Comments
 (0)