Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions docs/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ A recipe is comprised of the following yaml files in a directory:
* `compilers.yaml`: the compilers provided by the stack.
* `environments.yaml`: environments that contain all the software packages.
* `modules.yaml`: _optional_ module generation rules
* follows the spec for [spack module configuration](https://spack.readthedocs.io/en/latest/module_file_support.html)
* 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).
* `packages.yaml`: _optional_ define external packages
* follows the spec for [spack package configuration](https://spack.readthedocs.io/en/latest/build_settings.html)
* `repo`: _optional_ custom spack package definitions.
Expand Down Expand Up @@ -445,7 +445,14 @@ The presence of a `modules.yaml` file in the recipe is a necessary and sufficien
!!! warning
`config:modules` field has been deprecated. It can still be specified, but it has to be consistent with the presence of `modules.yaml` file.

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).
Modules are generated for the installed compilers and packages by spack.

!!! info "Rules for module generation in `modules.yaml`"
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).
But, **there are some differences**:

- `modules:default:arch_folder` defaults to `false`. If set to `true` an error is raised, as Stackinator does not support this feature;
- `modules:default:roots:tcl` is ignored, as Stackinator automatically configures the module root to be inside the uenv mount point.

## Custom Spack Packages

Expand Down
10 changes: 8 additions & 2 deletions stackinator/recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,14 @@ def __init__(self, args):
if modules_path.is_file():
with modules_path.open() as fid:
self.modules = yaml.load(fid, Loader=yaml.Loader)
# Note: it should match MODULEPATH set by envvars and used by uenv view "modules"
self.modules["modules"]["default"]["roots"]["tcl"] = (pathlib.Path(self.mount) / "modules").as_posix()
schema.ModulesValidator.validate(self.modules)

# Note:
# modules root should match MODULEPATH set by envvars and used by uenv view "modules"
# so we enforce that the user does not override it in modules.yaml
self.modules["modules"].setdefault("default", {}).setdefault("roots", {}).setdefault(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a more robust solution 👍

"tcl", (self.mount / "modules").as_posix()
)

# DEPRECATED field `config:modules`
if "modules" in self.config:
Expand Down
9 changes: 9 additions & 0 deletions stackinator/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,16 @@ def check_config_version(instance):
raise RuntimeError("incompatible uenv recipe version")


def check_module_paths(instance):
try:
instance["modules"]["default"]["roots"]["tcl"]
root_logger.warning("'modules:default:roots:tcl' field is ignored and overwritten by stackinator.")
except KeyError:
pass


ConfigValidator = SchemaValidator(prefix / "schema/config.json", check_config_version)
CompilersValidator = SchemaValidator(prefix / "schema/compilers.json")
EnvironmentsValidator = SchemaValidator(prefix / "schema/environments.json")
CacheValidator = SchemaValidator(prefix / "schema/cache.json")
ModulesValidator = SchemaValidator(prefix / "schema/modules.json", check_module_paths)
34 changes: 34 additions & 0 deletions stackinator/schema/modules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Schema for Stackinator constraints on Spack Modules modules.yaml",
"type" : "object",
"required": ["modules"],
"properties" : {
"modules" : {
"type": "object",
"properties": {
"default": {
"type": "object",
"default": {"arch_folder": false},
"properties": {
"roots": {
"type": "object",
"additionalProperties": true,
"properties": {
"tcl": {
"type": "string"
}
}
},
"arch_folder": {
"type": "boolean",
"const": false,
"default": false
}
}
}
}
}
}
}

73 changes: 73 additions & 0 deletions unittests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,76 @@ def test_recipe_environments_yaml(recipe_paths):
with open(p / "environments.yaml") as fid:
raw = yaml.load(fid, Loader=yaml.Loader)
schema.EnvironmentsValidator.validate(raw)


@pytest.mark.parametrize(
"recipe",
[
dedent(
"""
modules: {}
"""
),
dedent(
"""
modules:
default:
arch_folder: false
"""
),
dedent(
"""
modules:
# Paths tomodules: check when creating modules for all module sets
prefix_insmodules:pections:
bin:
- PATH
lib:
- LD_LIBRARY_PATH
lib64:
- LD_LIBRARY_PATH

default:
arch_folder: false
# Where to install modules
tcl:
all:
autoload: none
hash_length: 0
exclude_implicits: true
exclude: []
projections:
all: '{name}/{version}'
"""
),
dedent(
"""
modules:
default:
roots:
tcl: /path/which/is/going/to/be/ignored
"""
),
],
)
def test_valid_modules_yaml(recipe):
instance = yaml.load(recipe, Loader=yaml.Loader)
schema.ModulesValidator.validate(instance)
assert not instance["modules"]["default"]["arch_folder"]


@pytest.mark.parametrize(
"recipe",
[
dedent(
"""
modules:
default:
arch_folder: true
"""
),
],
)
def test_invalid_modules_yaml(recipe):
with pytest.raises(Exception):
schema.ModulesValidator.validate(yaml.load(recipe, Loader=yaml.Loader))