Skip to content

Commit 2b0b8a0

Browse files
authored
fix env_vars: preprend_path and append_path were doing the opposite (eth-cscs#285)
This fixes envvars prepend/append behavior. Indeed, it was doing the exact opposite, i.e. prepending vars specified under `append_path` and appending vars under `prepend_path`. The actual fix is just the first commit 5a1d620, but I also took the chance to refactor the code aiming at better isolation for testing purposes.
1 parent bdf27d0 commit 2b0b8a0

2 files changed

Lines changed: 70 additions & 55 deletions

File tree

stackinator/etc/envvars.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
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

stackinator/recipe.py

Lines changed: 13 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import yaml
77

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

1111

1212
class Recipe:
@@ -303,60 +303,18 @@ def environment_view_meta(self):
303303
view_meta = {}
304304
for _, env in self.environments.items():
305305
for view in env["views"]:
306-
# recipe authors can substitute the name of the view, the mount
307-
# and view path into environment variables using '$@key@' where
308-
# key is one of view_name, mount and view_path.
309-
substitutions = {
310-
"view_name": str(view["name"]),
311-
"mount": str(self.mount),
312-
"view_path": str(view["config"]["root"]),
313-
}
314-
315-
def fill(s):
316-
return re.sub(
317-
r"\$@(\w+)@",
318-
lambda m: substitutions.get(m.group(1), m.group(0)),
319-
s,
320-
)
321-
322-
ev_inputs = view["extra"]["env_vars"]
323-
env = envvars.EnvVarSet()
324-
325-
# TODO: one day this code will be revisited because we need to append_path
326-
# or prepend_path to a variable that isn't in envvars.is_list_var
327-
# On that day, extend the environments.yaml views:uenv:env_vars field
328-
# to also accept a list of env var names to add to the blessed list of prefix paths
329-
330-
for v in ev_inputs["set"]:
331-
((name, value),) = v.items()
332-
if value is not None:
333-
value = fill(value)
334-
335-
# insist that the only 'set' operation on prefix variables is to unset/reset them
336-
# this requires that users use append and prepend to build up the variables
337-
if envvars.is_list_var(name) and value is not None:
338-
raise RuntimeError(f"{name} in the {view['name']} view is a prefix variable.")
339-
else:
340-
if envvars.is_list_var(name):
341-
env.set_list(name, [], envvars.EnvVarOp.SET)
342-
else:
343-
env.set_scalar(name, value)
344-
for v in ev_inputs["prepend_path"]:
345-
((name, value),) = v.items()
346-
if value is not None:
347-
value = fill(value)
348-
if not envvars.is_list_var(name):
349-
raise RuntimeError(f"{name} in the {view['name']} view is not a known prefix path variable")
350-
351-
env.set_list(name, [value], envvars.EnvVarOp.APPEND)
352-
for v in ev_inputs["append_path"]:
353-
((name, value),) = v.items()
354-
if value is not None:
355-
value = fill(value)
356-
if not envvars.is_list_var(name):
357-
raise RuntimeError(f"{name} in the {view['name']} view is not a known prefix path variable")
358-
359-
env.set_list(name, [value], envvars.EnvVarOp.PREPEND)
306+
try:
307+
# recipe authors can substitute the name of the view, the mount
308+
# and view path into environment variables using '$@key@' where
309+
# key is one of view_name, mount and view_path.
310+
substitutions = {
311+
"view_name": str(view["name"]),
312+
"mount": str(self.mount),
313+
"view_path": str(view["config"]["root"]),
314+
}
315+
env = EnvVarSet.from_envvars(view["extra"]["env_vars"], substitutions)
316+
except Exception as err:
317+
raise RuntimeError(f'In view "{view["name"]}": {err}')
360318

361319
view_meta[view["name"]] = {
362320
"root": view["config"]["root"],

0 commit comments

Comments
 (0)