Skip to content

Commit c7db13c

Browse files
authored
configure-time view variable templating (#282)
Configure time string substitution of mount and view information for environment variables defined in the `env:views:view:env_vars` field. Substition of a `key` is performed for templates of the form `$@key@`, e.g.: ```yaml cuda-env: views: env_vars: set: - ACTIVATE: "$@mount@/activate.sh" ``` The following keys are supported, with examples: ``` mount. /user-environment view_name cuda-env view_path /user-environment/env/cuda-env ```
1 parent 9f305a4 commit c7db13c

File tree

4 files changed

+57
-6
lines changed

4 files changed

+57
-6
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ site
1818
# distribution/packaging files
1919
*.egg-info/
2020
build/
21+
uv.lock
22+
.venv*

bin/stack-config

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
1-
#!/usr/bin/env bash
1+
#!/usr/bin/env -S uv run --script
2+
# /// script
3+
# requires-python = ">=3.12"
4+
# dependencies = [
5+
# "jinja2",
6+
# "jsonschema",
7+
# "pyYAML",
8+
# ]
9+
# ///
210

3-
export UV_PROJECT_ENVIRONMENT=.venv-`uname -m`
11+
import pathlib
12+
import sys
413

5-
STACKINATOR_ROOT=$(dirname `realpath $0`)/..
6-
uv run --directory $STACKINATOR_ROOT --with . python -m stackinator.main $@
14+
prefix = pathlib.Path(__file__).parent.parent.resolve()
15+
sys.path = [prefix.as_posix()] + sys.path
16+
17+
from stackinator.main import main
18+
19+
# Once we've set up the system path, run the tool's main method
20+
if __name__ == "__main__":
21+
sys.exit(main())

docs/recipes.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,12 +405,24 @@ The `set` field is a list of environment variables key-value pairs that specify
405405
* It is not possible to set an initial value that is not `null` for a prefix path variable.
406406
Set such variables to `null` (unset it), then provide `append_path` and `prefix_path` operations below to set the individual paths.
407407

408-
!!! note "using `${@VAR@}` to use environment variables"
408+
!!! info "use `${@VAR@}` to set environment variables at runtime"
409409
Sometimes you want to compose an environment variable **that has been set in the runtime environment** in your environment variable definition.
410410
For example, every user has a different `HOME` or `SCRATCH` value, and you might want to configure your view to store / read configuration from this path.
411411
The special syntax `${@VAR@}` will defer expanding the environment variable `VAR` until the view is loaded by uenv.
412412
The example above shows how to set the Juliaup install directory to be in the user's local scratch, i.e. a personalised private location for each user.
413413

414+
!!! info "use `$@var@` to configure environment variables at configure time"
415+
The special syntax `$@var@` can be used to substitute information about the view when configuring the recipe.
416+
This is useful if you want to set an environment variable that refers to the mount point or mounted location of the view.
417+
418+
The following values are available:
419+
420+
| key | description |
421+
| --- | ----------- |
422+
| `mount` | the mount point of the image, e.g. `/user-environment` |
423+
| `view_name` | the name of the view, e.g. `cuda-env` in the example above |
424+
| `view_path` | the prefix path of the view, e.g. `/user-environment/env/cuda-env` |
425+
414426
The `prepend_path` field takes a list of key-value pairs that define paths to prepend to a prefix path variable.
415427

416428
* Each entry is a single path

stackinator/recipe.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,22 @@ def environment_view_meta(self):
292292
view_meta = {}
293293
for _, env in self.environments.items():
294294
for view in env["views"]:
295+
# recipe authors can substitute the name of the view, the mount
296+
# and view path into environment variables using '$@key@' where
297+
# key is one of view_name, mount and view_path.
298+
substitutions = {
299+
"view_name": str(view["name"]),
300+
"mount": str(self.mount),
301+
"view_path": str(view["config"]["root"]),
302+
}
303+
304+
def fill(s):
305+
return re.sub(
306+
r"\$@(\w+)@",
307+
lambda m: substitutions.get(m.group(1), m.group(0)),
308+
s,
309+
)
310+
295311
ev_inputs = view["extra"]["env_vars"]
296312
env = envvars.EnvVarSet()
297313

@@ -302,6 +318,9 @@ def environment_view_meta(self):
302318

303319
for v in ev_inputs["set"]:
304320
((name, value),) = v.items()
321+
if value is not None:
322+
value = fill(value)
323+
305324
# insist that the only 'set' operation on prefix variables is to unset/reset them
306325
# this requires that users use append and prepend to build up the variables
307326
if envvars.is_list_var(name) and value is not None:
@@ -313,20 +332,23 @@ def environment_view_meta(self):
313332
env.set_scalar(name, value)
314333
for v in ev_inputs["prepend_path"]:
315334
((name, value),) = v.items()
335+
if value is not None:
336+
value = fill(value)
316337
if not envvars.is_list_var(name):
317338
raise RuntimeError(f"{name} in the {view['name']} view is not a known prefix path variable")
318339

319340
env.set_list(name, [value], envvars.EnvVarOp.APPEND)
320341
for v in ev_inputs["append_path"]:
321342
((name, value),) = v.items()
343+
if value is not None:
344+
value = fill(value)
322345
if not envvars.is_list_var(name):
323346
raise RuntimeError(f"{name} in the {view['name']} view is not a known prefix path variable")
324347

325348
env.set_list(name, [value], envvars.EnvVarOp.PREPEND)
326349

327350
view_meta[view["name"]] = {
328351
"root": view["config"]["root"],
329-
"activate": view["config"]["root"] + "/activate.sh",
330352
"description": "", # leave the description empty for now
331353
"recipe_variables": env.as_dict(),
332354
}

0 commit comments

Comments
 (0)