Skip to content

Commit df79fd0

Browse files
rupertnashmr-c
authored andcommitted
Allow software requirements to modify env.
First do basic env setup, run the modification commands in a script and record the environment. Have the run_job script then use this environment (ignoring some variables).
1 parent a3e3a57 commit df79fd0

File tree

5 files changed

+114
-58
lines changed

5 files changed

+114
-58
lines changed

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ pylint_report.txt: $(PYSOURCES)
116116
$^ -j0> $@ || true
117117

118118
diff_pylint_report: pylint_report.txt
119-
diff-quality --violations=pylint pylint_report.txt
119+
diff-quality --compare-branch=main --violations=pylint pylint_report.txt
120120

121121
.coverage: testcov
122122

@@ -136,10 +136,10 @@ coverage-report: .coverage
136136
coverage report
137137

138138
diff-cover: coverage.xml
139-
diff-cover $^
139+
diff-cover --compare-branch=main $^
140140

141141
diff-cover.html: coverage.xml
142-
diff-cover $^ --html-report $@
142+
diff-cover --compare-branch=main $^ --html-report $@
143143

144144
## test : run the ${MODULE} test suite
145145
test: $(PYSOURCES)

README.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,13 @@ This option allows one to specify a dependency resolver's configuration file.
316316
This file may be specified as either XML or YAML and very simply describes various
317317
plugins to enable to "resolve" ``SoftwareRequirement`` dependencies.
318318

319+
Using these hints will allow cwltool to modify the environment in
320+
which your tool runs, for example by loading one or more environment
321+
modules. The enviroment is constructed as normal (i.e. standard CWL
322+
runtime enviroment, ``EnvVarRequirement``, ``--preserve-environment``
323+
are applied), then this is modified. This currently means that you
324+
cannot override variables set in this way.
325+
319326
To discuss some of these plugins and how to configure them, first consider the
320327
following ``hint`` definition for an example CWL tool.
321328

@@ -427,7 +434,7 @@ So consider the resolvers configuration file
427434
base_path: ./tests/test_deps_env
428435
mapping_files: ./tests/test_deps_mapping.yml
429436
430-
And the corresponding mapping configuraiton file (`tests/test_deps_mapping.yml`):
437+
And the corresponding mapping configuration file (`tests/test_deps_mapping.yml`):
431438

432439
.. code:: yaml
433440

cwltool/job.py

Lines changed: 3 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from schema_salad.utils import json_dump, json_dumps
3737
from typing_extensions import TYPE_CHECKING
3838

39+
from . import run_job
3940
from .builder import Builder, HasReqsHints
4041
from .context import RuntimeContext
4142
from .errors import UnsupportedRequirement, WorkflowException
@@ -62,57 +63,7 @@
6263
FORCE_SHELLED_POPEN = os.getenv("CWLTOOL_FORCE_SHELL_POPEN", "0") == "1"
6364

6465
SHELL_COMMAND_TEMPLATE = """#!/bin/bash
65-
python "run_job.py" "job.json"
66-
"""
67-
68-
PYTHON_RUN_SCRIPT = """
69-
import json
70-
import os
71-
import sys
72-
try:
73-
import subprocess32 as subprocess # type: ignore
74-
except Exception:
75-
import subprocess
76-
77-
with open(sys.argv[1], "r") as f:
78-
popen_description = json.load(f)
79-
commands = popen_description["commands"]
80-
cwd = popen_description["cwd"]
81-
env = popen_description["env"]
82-
env["PATH"] = os.environ.get("PATH")
83-
stdin_path = popen_description["stdin_path"]
84-
stdout_path = popen_description["stdout_path"]
85-
stderr_path = popen_description["stderr_path"]
86-
if stdin_path is not None:
87-
stdin = open(stdin_path, "rb")
88-
else:
89-
stdin = subprocess.PIPE
90-
if stdout_path is not None:
91-
stdout = open(stdout_path, "wb")
92-
else:
93-
stdout = sys.stderr
94-
if stderr_path is not None:
95-
stderr = open(stderr_path, "wb")
96-
else:
97-
stderr = sys.stderr
98-
sp = subprocess.Popen(commands,
99-
shell=False,
100-
close_fds=True,
101-
stdin=stdin,
102-
stdout=stdout,
103-
stderr=stderr,
104-
env=env,
105-
cwd=cwd)
106-
if sp.stdin:
107-
sp.stdin.close()
108-
rcode = sp.wait()
109-
if stdin is not subprocess.PIPE:
110-
stdin.close()
111-
if stdout is not sys.stderr:
112-
stdout.close()
113-
if stderr is not sys.stderr:
114-
stderr.close()
115-
sys.exit(rcode)
66+
python3 "run_job.py" "job.json"
11667
"""
11768

11869

@@ -988,8 +939,7 @@ def terminate(): # type: () -> None
988939
with open(job_script, "wb") as _:
989940
_.write(job_script_contents.encode("utf-8"))
990941
job_run = os.path.join(job_dir, "run_job.py")
991-
with open(job_run, "wb") as _:
992-
_.write(PYTHON_RUN_SCRIPT.encode("utf-8"))
942+
shutil.copyfile(run_job.__file__, job_run)
993943
sproc = subprocess.Popen( # nosec
994944
["bash", job_script.encode("utf-8")],
995945
shell=False, # nosec

cwltool/run_job.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""Only used when there is a job script or CWLTOOL_FORCE_SHELL_POPEN=1."""
2+
import json
3+
import os
4+
import subprocess # nosec
5+
import sys
6+
from typing import BinaryIO, Dict, List, Optional, TextIO, Union
7+
8+
9+
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
12+
if res.returncode != 0:
13+
sys.stderr.write(
14+
"Error while using SoftwareRequirements to modify environment\n"
15+
)
16+
return cwl_env
17+
env = cwl_env.copy()
18+
with open("output_environment.bash") as env_file:
19+
for line in env_file:
20+
key, val = line.split("=", 1)
21+
if key in ("_", "PWD", "SHLVL"):
22+
# Skip some variables that are meaningful to the shell
23+
continue
24+
env[key] = val[:-1] # remove trailing newline
25+
return env
26+
27+
28+
def main(argv: List[str]) -> int:
29+
"""
30+
Read in the configuration JSON and execute the commands.
31+
32+
The first argument is the path to the JSON dictionary file containing keys:
33+
"commands": an array of strings that represents the command line to run
34+
"cwd": A string specifying which directory to run in
35+
"env": a dictionary of strings containing the environment variables to set
36+
"stdin_path": a string (or a null) giving the path that should be piped to STDIN
37+
"stdout_path": a string (or a null) giving the path that should receive the STDOUT
38+
"stderr_path": a string (or a null) giving the path that should receive the STDERR
39+
40+
The second argument is optional, it specifes a shell script to execute prior,
41+
and the environment variables it sets will be combined with the environment
42+
variables from the "env" key in the JSON dictionary from the first argument.
43+
"""
44+
with open(argv[1]) as f:
45+
popen_description = json.load(f)
46+
commands = popen_description["commands"]
47+
cwd = popen_description["cwd"]
48+
env = popen_description["env"]
49+
env["PATH"] = os.environ.get("PATH")
50+
stdin_path = popen_description["stdin_path"]
51+
stdout_path = popen_description["stdout_path"]
52+
stderr_path = popen_description["stderr_path"]
53+
if stdin_path is not None:
54+
stdin: Union[BinaryIO, int] = open(stdin_path, "rb")
55+
else:
56+
stdin = subprocess.PIPE
57+
if stdout_path is not None:
58+
stdout: Union[BinaryIO, TextIO] = open(stdout_path, "wb")
59+
else:
60+
stdout = sys.stderr
61+
if stderr_path is not None:
62+
stderr: Union[BinaryIO, TextIO] = open(stderr_path, "wb")
63+
else:
64+
stderr = sys.stderr
65+
66+
try:
67+
env_script: Optional[str] = argv[2]
68+
except IndexError:
69+
env_script = None
70+
if env_script is not None:
71+
env = handle_software_environment(env, env_script)
72+
73+
sp = subprocess.Popen( # nosec
74+
commands,
75+
shell=False,
76+
close_fds=True,
77+
stdin=stdin,
78+
stdout=stdout,
79+
stderr=stderr,
80+
env=env,
81+
cwd=cwd,
82+
)
83+
if sp.stdin:
84+
sp.stdin.close()
85+
rcode = sp.wait()
86+
if not isinstance(stdin, int):
87+
stdin.close()
88+
if stdout is not sys.stderr:
89+
stdout.close()
90+
if stderr is not sys.stderr:
91+
stderr.close()
92+
return rcode
93+
94+
95+
if __name__ == "__main__":
96+
sys.exit(main(sys.argv))

cwltool/software_requirements.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@
3030

3131
COMMAND_WITH_DEPENDENCIES_TEMPLATE = string.Template(
3232
"""#!/bin/bash
33+
cat > modify_environment.bash <<'EOF'
3334
$handle_dependencies
34-
python3 "run_job.py" "job.json"
35+
env > output_environment.bash
36+
EOF
37+
python3 "run_job.py" "job.json" "modify_environment.bash"
3538
"""
3639
)
3740

0 commit comments

Comments
 (0)