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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ site
# distribution/packaging files
*.egg-info/
build/
uv.lock
.venv*
23 changes: 19 additions & 4 deletions bin/stack-config
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
#!/usr/bin/env bash
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "jinja2",
# "jsonschema",
# "pyYAML",
# ]
# ///

export UV_PROJECT_ENVIRONMENT=.venv-`uname -m`
import pathlib
import sys

STACKINATOR_ROOT=$(dirname `realpath $0`)/..
uv run --directory $STACKINATOR_ROOT --with . python -m stackinator.main $@
prefix = pathlib.Path(__file__).parent.parent.resolve()
sys.path = [prefix.as_posix()] + sys.path

from stackinator.main import main

# Once we've set up the system path, run the tool's main method
if __name__ == "__main__":
sys.exit(main())
14 changes: 13 additions & 1 deletion docs/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,12 +405,24 @@ The `set` field is a list of environment variables key-value pairs that specify
* It is not possible to set an initial value that is not `null` for a prefix path variable.
Set such variables to `null` (unset it), then provide `append_path` and `prefix_path` operations below to set the individual paths.

!!! note "using `${@VAR@}` to use environment variables"
!!! info "use `${@VAR@}` to set environment variables at runtime"
Sometimes you want to compose an environment variable **that has been set in the runtime environment** in your environment variable definition.
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.
The special syntax `${@VAR@}` will defer expanding the environment variable `VAR` until the view is loaded by uenv.
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.

!!! info "use `$@var@` to configure environment variables at configure time"
The special syntax `$@var@` can be used to substitute information about the view when configuring the recipe.
This is useful if you want to set an environment variable that refers to the mount point or mounted location of the view.

The following values are available:

| key | description |
| --- | ----------- |
| `mount` | the mount point of the image, e.g. `/user-environment` |
| `view_name` | the name of the view, e.g. `cuda-env` in the example above |
| `view_path` | the prefix path of the view, e.g. `/user-environment/env/cuda-env` |

The `prepend_path` field takes a list of key-value pairs that define paths to prepend to a prefix path variable.

* Each entry is a single path
Expand Down
24 changes: 23 additions & 1 deletion stackinator/recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,22 @@ def environment_view_meta(self):
view_meta = {}
for _, env in self.environments.items():
for view in env["views"]:
# recipe authors can substitute the name of the view, the mount
# and view path into environment variables using '$@key@' where
# key is one of view_name, mount and view_path.
substitutions = {
"view_name": str(view["name"]),
"mount": str(self.mount),
"view_path": str(view["config"]["root"]),
}

def fill(s):
return re.sub(
r"\$@(\w+)@",
lambda m: substitutions.get(m.group(1), m.group(0)),
s,
)

ev_inputs = view["extra"]["env_vars"]
env = envvars.EnvVarSet()

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

for v in ev_inputs["set"]:
((name, value),) = v.items()
if value is not None:
value = fill(value)

# insist that the only 'set' operation on prefix variables is to unset/reset them
# this requires that users use append and prepend to build up the variables
if envvars.is_list_var(name) and value is not None:
Expand All @@ -313,20 +332,23 @@ def environment_view_meta(self):
env.set_scalar(name, value)
for v in ev_inputs["prepend_path"]:
((name, value),) = v.items()
if value is not None:
value = fill(value)
if not envvars.is_list_var(name):
raise RuntimeError(f"{name} in the {view['name']} view is not a known prefix path variable")

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

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

view_meta[view["name"]] = {
"root": view["config"]["root"],
"activate": view["config"]["root"] + "/activate.sh",
"description": "", # leave the description empty for now
"recipe_variables": env.as_dict(),
}
Expand Down