Skip to content

Commit 82030b5

Browse files
rupertnashmr-c
authored andcommitted
Put the environment setup code into (mostly) one place.
1 parent a89c329 commit 82030b5

File tree

4 files changed

+80
-32
lines changed

4 files changed

+80
-32
lines changed

README.rst

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,30 @@ CWL documents can be expressed as RDF triple graphs.
296296
cwltool --print-rdf --rdf-serializer=turtle mywf.cwl
297297
298298
299+
Environment Variables in cwltool
300+
--------------------------------
301+
302+
This reference implementation supports several ways of setting
303+
enviroment variables for tools, in addition to the standard
304+
``EnvVarRequirement``. The sequence of steps applied to create the
305+
enviroment is:
306+
307+
0. If the ``--preserve-entire-environment`` flag is present, then begin with the current
308+
environment, else begin with an empty environment.
309+
310+
1. Add any variables specified by ``--preserve-environment`` option(s).
311+
312+
2. Set ``TMPDIR`` and ``HOME`` per `the CWL v1.0+ CommandLineTool specification <https://www.commonwl.org/v1.0/CommandLineTool.html#Runtime_environment>`_.
313+
314+
3. Apply any ``EnvVarRequirement`` from the ``CommandLineTool`` description.
315+
316+
4. Apply any manipulations required by any ``cwltool:MPIRequirement`` extensions.
317+
318+
5. Substitute any secrets required by ``Secrets`` extension.
319+
320+
6. Modify the environment in response to ``SoftwareRequirement`` (see below).
321+
322+
299323
Leveraging SoftwareRequirements (Beta)
300324
--------------------------------------
301325

@@ -318,10 +342,11 @@ plugins to enable to "resolve" ``SoftwareRequirement`` dependencies.
318342

319343
Using these hints will allow cwltool to modify the environment in
320344
which your tool runs, for example by loading one or more Environment
321-
Modules. The environment is constructed as normal (i.e. standard CWL
322-
runtime enviroment, ``EnvVarRequirement``, ``--preserve-environment``
323-
are applied), then the environment may modified by the selected tool resolver. This currently means that you
324-
cannot override any environment variables set by the selected tool resolver.
345+
Modules. The environment is constructed as above, then the environment
346+
may modified by the selected tool resolver. This currently means that
347+
you cannot override any environment variables set by the selected tool
348+
resolver. Note that the enviroment given to the configured dependency
349+
resolver has the variable `_CWLTOOL` set to `1` to allow introspection.
325350

326351
To discuss some of these plugins and how to configure them, first consider the
327352
following ``hint`` definition for an example CWL tool.

cwltool/command_line_tool.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -995,13 +995,14 @@ def register_reader(f: CWLObjectType) -> None:
995995
"networkAccess must be a boolean, got: %s" % j.networkaccess
996996
)
997997

998-
j.environment = {}
998+
# Build a mapping to hold any EnvVarRequirement
999+
required_env = {}
9991000
evr, _ = self.get_requirement("EnvVarRequirement")
10001001
if evr is not None:
10011002
for t3 in cast(List[Dict[str, str]], evr["envDef"]):
1002-
j.environment[t3["envName"]] = cast(
1003-
str, builder.do_eval(t3["envValue"])
1004-
)
1003+
required_env[t3["envName"]] = cast(str, builder.do_eval(t3["envValue"]))
1004+
# Construct the env
1005+
j.prepare_environment(runtimeContext, required_env)
10051006

10061007
shellcmd, _ = self.get_requirement("ShellCommandRequirement")
10071008
if shellcmd is not None:

cwltool/job.py

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
Callable,
2020
Iterable,
2121
List,
22+
Mapping,
2223
Match,
2324
MutableMapping,
2425
MutableSequence,
@@ -419,6 +420,42 @@ def _execute(
419420
)
420421
shutil.rmtree(self.tmpdir, True)
421422

423+
def prepare_environment(
424+
self, runtimeContext: RuntimeContext, envVarReq: Mapping[str, str]
425+
) -> None:
426+
"""Set up environment variables.
427+
428+
Here we prepare the environment for the job, based on any
429+
preserved variables and `EnvVarRequirement`. Later, changes due
430+
to `MPIRequirement`, `Secrets`, or `SoftwareRequirement` are
431+
applied (in that order).
432+
"""
433+
# Start empty
434+
env = {}
435+
436+
# Preserve any env vars
437+
vars_to_preserve = runtimeContext.preserve_environment
438+
if runtimeContext.preserve_entire_environment is not False:
439+
vars_to_preserve = os.environ
440+
if vars_to_preserve:
441+
for key, value in os.environ.items():
442+
if key in vars_to_preserve and key not in env:
443+
env[key] = value
444+
445+
# Set required env vars
446+
env["HOME"] = self.outdir
447+
env["TMPDIR"] = self.tmpdir
448+
if "PATH" not in env:
449+
env["PATH"] = os.environ["PATH"]
450+
if "SYSTEMROOT" not in env and "SYSTEMROOT" in os.environ:
451+
env["SYSTEMROOT"] = os.environ["SYSTEMROOT"]
452+
453+
# Apply EnvVarRequirement
454+
env.update(envVarReq)
455+
456+
# Set on ourselves
457+
self.environment = env
458+
422459
def process_monitor(self, sproc): # type: (subprocess.Popen[str]) -> None
423460
monitor = psutil.Process(sproc.pid)
424461
# Value must be list rather than integer to utilise pass-by-reference in python
@@ -469,19 +506,6 @@ def run(
469506

470507
self._setup(runtimeContext)
471508

472-
env = self.environment
473-
vars_to_preserve = runtimeContext.preserve_environment
474-
if runtimeContext.preserve_entire_environment is not False:
475-
vars_to_preserve = os.environ
476-
if vars_to_preserve:
477-
for key, value in os.environ.items():
478-
if key in vars_to_preserve and key not in env:
479-
env[key] = value
480-
env["HOME"] = self.outdir
481-
env["TMPDIR"] = self.tmpdir
482-
if "PATH" not in env:
483-
env["PATH"] = os.environ["PATH"]
484-
485509
stage_files(
486510
self.pathmapper,
487511
ignore_writable=True,
@@ -504,7 +528,7 @@ def run(
504528

505529
monitor_function = functools.partial(self.process_monitor)
506530

507-
self._execute([], env, runtimeContext, monitor_function)
531+
self._execute([], self.environment, runtimeContext, monitor_function)
508532

509533

510534
CONTROL_CODE_RE = r"\x1b\[[0-9;]*[a-zA-Z]"
@@ -832,7 +856,7 @@ def _job_popen(
832856
stdin_path: Optional[str],
833857
stdout_path: Optional[str],
834858
stderr_path: Optional[str],
835-
env: MutableMapping[str, str],
859+
env: Mapping[str, str],
836860
cwd: str,
837861
make_job_dir: Callable[[], str],
838862
job_script_contents: Optional[str] = None,
@@ -915,15 +939,10 @@ def terminate(): # type: () -> None
915939
if job_script_contents is None:
916940
job_script_contents = SHELL_COMMAND_TEMPLATE
917941

918-
env_copy = {}
919-
key = None # type: Optional[str]
920-
for key in env:
921-
env_copy[key] = env[key]
922-
923942
job_description = {
924943
"commands": commands,
925944
"cwd": cwd,
926-
"env": env_copy,
945+
"env": env,
927946
"stdout_path": stdout_path,
928947
"stderr_path": stderr_path,
929948
"stdin_path": stdin_path,

cwltool/run_job.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@
77

88

99
def handle_software_environment(cwl_env: Dict[str, str], script: str) -> Dict[str, str]:
10-
"""Update the provided environment variables dictiony by running the script."""
11-
res = subprocess.run(["bash", script], shell=False, env=cwl_env) # nosec
10+
"""Update the provided environment dict by running the script."""
11+
exec_env = cwl_env.copy()
12+
exec_env["_CWLTOOL"] = "1"
13+
res = subprocess.run(["bash", script], shell=False, env=exec_env) # nosec
1214
if res.returncode != 0:
1315
sys.stderr.write(
1416
"Error while using SoftwareRequirements to modify environment\n"
1517
)
1618
return cwl_env
19+
1720
env = cwl_env.copy()
1821
with open("output_environment.bash") as env_file:
1922
for line in env_file:
2023
key, val = line.split("=", 1)
21-
if key in ("_", "PWD", "SHLVL", "TMPDIR", "HOME"):
24+
if key in ("_", "PWD", "SHLVL", "TMPDIR", "HOME", "_CWLTOOL"):
2225
# Skip some variables that are meaningful to the shell
2326
# or set specifically by the CWL runtime environment.
2427
continue

0 commit comments

Comments
 (0)