Skip to content

Commit daa8cff

Browse files
committed
sync with upstream
2 parents 41e7630 + 30ab4ee commit daa8cff

21 files changed

Lines changed: 214 additions & 149 deletions

docs/recipes.md

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ A recipe is comprised of the following yaml files in a directory:
1515
* `post-install`: _optional_ a script to run after Spack has been executed to build the stack.
1616
* `pre-install`: _optional_ a script to run before any packages have been built.
1717

18+
[](){#ref-recipes-config}
1819
## Configuration
1920

2021
```yaml title="config.yaml"
@@ -26,17 +27,18 @@ spack:
2627
packages:
2728
repo: https://github.com/spack/spack-packages.git
2829
commit: develop
29-
modules: true
3030
description: "HPC development tools for building MPI applications with the GNU compiler toolchain"
31+
default-view: develop
3132
version: 2
3233
```
3334
3435
* `name`: a plain text name for the environment
3536
* `store`: the location where the environment will be mounted.
3637
* `spack`: which spack and package repositories to use for installation.
37-
* `modules`: (_deprecated_) _optional_ enable/disable module file generation.
3838
* `description`: _optional_ a string that describes the environment (default empty).
39+
* `default-view`: _default = null_ the name of a uenv view to load if no view is explicitly requested by the user. See the documentation for [default views][ref-recipes-default-view]. If no default view is specified, none will be set.
3940
* `version`: _default = 1_ the version of the uenv recipe (see below)
41+
* `modules`: (_deprecated_) _optional_ enable/disable module file generation.
4042

4143
!!! note "uenv recipe versions"
4244
Stackinator 6 introduces breaking changes to the uenv recipe format, introduced to support Spack v1.0.
@@ -350,27 +352,34 @@ For example, the `views` description:
350352
```yaml
351353
cuda-env:
352354
views:
353-
default:
355+
full:
354356
no-python:
355357
exclude:
356358
- 'python'
357359
```
358360

359361
will configure two views:
360362

361-
* `default`: a view of all the software in the environment using the default settings of Spack.
362-
* `no-python`: everything in the default view, except any versions of `python`.
363+
* `full`: a view of all the software in the environment using the full settings of Spack.
364+
* `no-python`: everything in the `full` view, except any versions of `python`.
365+
366+
!!! warning "only name views default if they are loaded by default"
367+
It is possible to name views `default`, and there are uenv images like `prgenv-gnu` that follow this practice for legacy reasons.
368+
369+
However the name default can be confusing for users because it implies that the view is loaded by default.
370+
However, [default views][ref-recipes-default-view] must be explicitly set in [`config.yaml`][ref-recipes-config].
363371

364372
Stackinator provides some additional options that are not provided by Spack, to fine tune the view, that can be set in the `uenv:` field:
365373

366374
```yaml
367375
cuda-env:
368376
views:
369-
uenv:
370-
add_compilers: true
371-
prefix_paths:
372-
LD_LIBRARY_PATH: [lib, lib64]
373-
env_vars:
377+
my-view:
378+
uenv:
379+
add_compilers: true
380+
prefix_paths:
381+
LD_LIBRARY_PATH: [lib, lib64]
382+
env_vars:
374383
set:
375384
- WOMBAT: null
376385
- NOCOLOR: "1"
@@ -392,6 +401,18 @@ cuda-env:
392401
!!! info
393402
See the [interfaces documentation](interfaces.md#environment-views) for more information about how the environment views are provided.
394403

404+
[](){#ref-recipes-default-view}
405+
#### Setting default views
406+
407+
!!! info "available in uenv 9.3 and later"
408+
Default views are only loaded in recent versions of uenv.
409+
Older uenv versions will ignore the default view - it is not an error to load a uenv that provides a default view using uenv 9.2 and older.
410+
411+
It is possible to specify a view that will be loaded automatically if no view is specified, by setting the `default-view` field in [`config.yaml` file][ref-recipes-config].
412+
413+
* a default view is only used if it is explicitly specified in `config.yaml`.
414+
* if a uenv is started with a view specified, the default view is ignored.
415+
395416
#### Setting environment variables with `env_vars`
396417

397418
The `views:<view_name>:uenv:env_vars` field can be used to further fine-tune the environment variables that are set when the view is started.
@@ -545,7 +566,7 @@ The post-install script is templated using Jinja, with the following variables a
545566
The use of Jinja templates is demonstrated in the following example of a bash script that generates an activation script that adds the installation path of GROMACS to the system PATH:
546567

547568
```bash title="post-install script that generates a simple activation script."
548-
#!/bin/bash
569+
#!/usr/bin/env bash
549570
550571
gmx_path=$(spack -C {{ env.config }} location -i gromacs)/bin
551572
echo "export PATH=$gmx_path:$PATH" >> {{ env.mount }}/activate.sh

stackinator/builder.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ def environment_meta(self, recipe):
156156
meta["name"] = conf["name"]
157157
meta["description"] = conf["description"]
158158
meta["views"] = recipe.environment_view_meta
159+
meta["default-view"] = recipe.default_view
159160
meta["mount"] = str(recipe.mount)
160161
modules = None
161162
if recipe.with_modules:

stackinator/etc/Make.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ store:
2929
touch $@
3030

3131
# Because Spack doesn't know how to find compilers, we help it by getting the bin folder of gcc, clang, nvc given a install prefix
32-
compiler_bin_dirs = $$(find $(1) '(' -name gcc -o -name clang -o -name nvc ')' -path '*/bin/*' '(' -type f -o -type l ')' -exec dirname {} +)
32+
compiler_bin_dirs = $$(find $(1) '(' -name gcc -o -name clang -o -name nvc -o -name icx ')' -path '*/bin/*' '(' -type f -o -type l ')' -exec dirname {} +)

stackinator/etc/bwrap-mutable-root.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/usr/bin/env bash
22
args=()
33
shopt -s dotglob
44
for d in /*; do

stackinator/etc/envvars.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
#!/usr/bin/python3
1+
#!/usr/bin/env python3
22

33
import argparse
44
import json
55
import os
6+
import re
67
from enum import Enum
78
from typing import List, Optional
89

@@ -206,6 +207,11 @@ def is_list_var(name: str) -> bool:
206207
return name in list_variables
207208

208209

210+
class VarExpansionError(Exception):
211+
def __init__(self, variable_name) -> None:
212+
super().__init__(f'"{variable_name}" variable cannot be expanded')
213+
214+
209215
class EnvVarSet:
210216
"""
211217
A set of environment variable updates.
@@ -219,6 +225,57 @@ def __init__(self):
219225
# toggles whether post export commands will be generated
220226
self._generate_post = True
221227

228+
@classmethod
229+
def from_envvars(cls, input: dict, substitutions: dict):
230+
def expand_vars(s):
231+
def var_expansion(m: re.Match):
232+
try:
233+
return substitutions[m.group(1)]
234+
except KeyError:
235+
raise VarExpansionError(m.group(0))
236+
237+
return re.sub(r"\$@(\w+)@", var_expansion, s)
238+
239+
# TODO: one day this code will be revisited because we need to append_path
240+
# or prepend_path to a variable that isn't in envvars.is_list_var
241+
# On that day, extend the environments.yaml views:uenv:env_vars field
242+
# to also accept a list of env var names to add to the blessed list of prefix paths
243+
244+
env = EnvVarSet()
245+
246+
for v in input.get("set", []):
247+
((name, value),) = v.items()
248+
if value is not None:
249+
value = expand_vars(value)
250+
251+
# insist that the only 'set' operation on prefix variables is to unset/reset them
252+
# this requires that users use append and prepend to build up the variables
253+
if is_list_var(name) and value is not None:
254+
raise RuntimeError(f"{name} is a prefix variable")
255+
else:
256+
if is_list_var(name):
257+
env.set_list(name, [], EnvVarOp.SET)
258+
else:
259+
env.set_scalar(name, value)
260+
261+
for v in input.get("prepend_path", []):
262+
((name, value),) = v.items()
263+
if value is not None:
264+
value = expand_vars(value)
265+
if not is_list_var(name):
266+
raise RuntimeError(f"{name} is not a known prefix path variable")
267+
env.set_list(name, [value], EnvVarOp.PREPEND)
268+
269+
for v in input.get("append_path", []):
270+
((name, value),) = v.items()
271+
if value is not None:
272+
value = expand_vars(value)
273+
if not is_list_var(name):
274+
raise RuntimeError(f"{name} is not a known prefix path variable")
275+
env.set_list(name, [value], EnvVarOp.APPEND)
276+
277+
return env
278+
222279
@property
223280
def lists(self):
224281
return self._lists
@@ -541,7 +598,7 @@ def meta_impl(args):
541598
for var_name, value in recipe_vars["scalar"].items():
542599
spack_vars["values"]["scalar"][var_name] = value
543600
for var_name, updates in recipe_vars["list"].items():
544-
spack_vars["values"]["list"].setdefault(name, [])
601+
spack_vars["values"]["list"].setdefault(var_name, [])
545602
spack_vars["values"]["list"][var_name] += updates
546603

547604
# update the global meta data to include the environment variable state

stackinator/recipe.py

Lines changed: 47 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import jinja2
66
import yaml
77

8-
from . import root_logger, schema, spack_util, mirror
9-
from .etc import envvars
8+
from . import cache, root_logger, schema, spack_util, mirror
9+
from .etc.envvars import EnvVarSet
1010

1111

1212
class Recipe:
@@ -87,14 +87,6 @@ def __init__(self, args):
8787
self._logger.error(f"modules.yaml:{self.with_modules}")
8888
raise RuntimeError("conflicting modules configuration detected")
8989

90-
# optional packages.yaml file
91-
packages_path = self.path / "packages.yaml"
92-
self._logger.debug(f"opening {packages_path}")
93-
self.packages = None
94-
if packages_path.is_file():
95-
with packages_path.open() as fid:
96-
self.packages = yaml.load(fid, Loader=yaml.Loader)
97-
9890
self._logger.debug("creating packages")
9991

10092
# load recipe/packages.yaml -> recipe_packages (if it exists)
@@ -169,8 +161,23 @@ def __init__(self, args):
169161
schema.EnvironmentsValidator.validate(raw)
170162
self.generate_environment_specs(raw)
171163

164+
# check that the default view exists (if one has been set)
165+
self._default_view = self.config["default-view"]
166+
if self._default_view is not None:
167+
available_views = [view["name"] for env in self.environments.values() for view in env["views"]]
168+
# add the modules and spack views to the list of available views
169+
if self.with_modules:
170+
available_views.append("modules")
171+
available_views.append("spack")
172+
if self._default_view not in available_views:
173+
self._logger.error(
174+
f"The default-view {self._default_view} is not the name of a view in the environments.yaml "
175+
"definition (one of {[name for name in available_views]}"
176+
)
177+
raise RuntimeError("Ivalid default-view in the recipe.")
178+
172179
# load the optional mirrors.yaml from system config, and add any additional
173-
# mirrors specified on the command line.
180+
# mirrors specified on the command line.
174181
self._logger.debug("Configuring mirrors.")
175182
self.mirrors = mirror.Mirrors(self.system_config_path, pathlib.Path(args.cache) if args.cache else None)
176183

@@ -262,66 +269,28 @@ def with_modules(self) -> bool:
262269
def find_spack_version(self, develop):
263270
return "1.0"
264271

272+
@property
273+
def default_view(self):
274+
return self._default_view
275+
265276
@property
266277
def environment_view_meta(self):
267278
# generate the view meta data that is presented in the squashfs image meta data
268279
view_meta = {}
269280
for _, env in self.environments.items():
270281
for view in env["views"]:
271-
# recipe authors can substitute the name of the view, the mount
272-
# and view path into environment variables using '$@key@' where
273-
# key is one of view_name, mount and view_path.
274-
substitutions = {
275-
"view_name": str(view["name"]),
276-
"mount": str(self.mount),
277-
"view_path": str(view["config"]["root"]),
278-
}
279-
280-
def fill(s):
281-
return re.sub(
282-
r"\$@(\w+)@",
283-
lambda m: substitutions.get(m.group(1), m.group(0)),
284-
s,
285-
)
286-
287-
ev_inputs = view["extra"]["env_vars"]
288-
env = envvars.EnvVarSet()
289-
290-
# TODO: one day this code will be revisited because we need to append_path
291-
# or prepend_path to a variable that isn't in envvars.is_list_var
292-
# On that day, extend the environments.yaml views:uenv:env_vars field
293-
# to also accept a list of env var names to add to the blessed list of prefix paths
294-
295-
for v in ev_inputs["set"]:
296-
((name, value),) = v.items()
297-
if value is not None:
298-
value = fill(value)
299-
300-
# insist that the only 'set' operation on prefix variables is to unset/reset them
301-
# this requires that users use append and prepend to build up the variables
302-
if envvars.is_list_var(name) and value is not None:
303-
raise RuntimeError(f"{name} in the {view['name']} view is a prefix variable.")
304-
else:
305-
if envvars.is_list_var(name):
306-
env.set_list(name, [], envvars.EnvVarOp.SET)
307-
else:
308-
env.set_scalar(name, value)
309-
for v in ev_inputs["prepend_path"]:
310-
((name, value),) = v.items()
311-
if value is not None:
312-
value = fill(value)
313-
if not envvars.is_list_var(name):
314-
raise RuntimeError(f"{name} in the {view['name']} view is not a known prefix path variable")
315-
316-
env.set_list(name, [value], envvars.EnvVarOp.APPEND)
317-
for v in ev_inputs["append_path"]:
318-
((name, value),) = v.items()
319-
if value is not None:
320-
value = fill(value)
321-
if not envvars.is_list_var(name):
322-
raise RuntimeError(f"{name} in the {view['name']} view is not a known prefix path variable")
323-
324-
env.set_list(name, [value], envvars.EnvVarOp.PREPEND)
282+
try:
283+
# recipe authors can substitute the name of the view, the mount
284+
# and view path into environment variables using '$@key@' where
285+
# key is one of view_name, mount and view_path.
286+
substitutions = {
287+
"view_name": str(view["name"]),
288+
"mount": str(self.mount),
289+
"view_path": str(view["config"]["root"]),
290+
}
291+
env = EnvVarSet.from_envvars(view["extra"]["env_vars"], substitutions)
292+
except Exception as err:
293+
raise RuntimeError(f'In view "{view["name"]}": {err}')
325294

326295
view_meta[view["name"]] = {
327296
"root": view["config"]["root"],
@@ -398,6 +367,10 @@ def generate_environment_specs(self, raw):
398367
# Which will compile the upstream MPI with nvfortran, as well as downstream dependendencies.
399368
if config["prefer"] is None:
400369
compiler = config["compiler"][0]
370+
# spack uses a different name for the intel oneapi compilers
371+
# than the package that installs them.
372+
if compiler == "intel-oneapi-compilers":
373+
compiler = "oneapi"
401374
config["prefer"] = [
402375
f"%[when=%c] c={compiler} %[when=%cxx] cxx={compiler} %[when=%fortran] fortran={compiler}"
403376
]
@@ -484,6 +457,15 @@ def generate_compiler_specs(self, raw):
484457
llvm_amdgpu["exclude_from_cache"] = cache_exclude
485458
compilers["llvm-amdgpu"] = llvm_amdgpu
486459

460+
if raw["intel-oneapi-compilers"] is not None:
461+
oneapi = {}
462+
oneapi_version = raw["intel-oneapi-compilers"]["version"]
463+
oneapi["packages"] = False
464+
oneapi["specs"] = [f"intel-oneapi-compilers@{oneapi_version}"]
465+
466+
oneapi["exclude_from_cache"] = cache_exclude
467+
compilers["intel-oneapi-compilers"] = oneapi
468+
487469
self.compilers = compilers
488470

489471
# The path of the default configuration for the target system/cluster

stackinator/schema/compilers.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,22 @@
6060
}
6161
],
6262
"default": null
63+
},
64+
"intel-oneapi-compilers": {
65+
"oneOf": [
66+
{
67+
"type": "object",
68+
"properties": {
69+
"version": {"type": "string"}
70+
},
71+
"additionalProperties": false,
72+
"required": ["version"]
73+
},
74+
{
75+
"type": "null"
76+
}
77+
],
78+
"default": null
6379
}
6480
}
6581
}

0 commit comments

Comments
 (0)