Skip to content

Commit bef5915

Browse files
authored
Merge branch 'master' into fix-671
2 parents c454528 + 353dbed commit bef5915

24 files changed

+1419
-1416
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ MODULE=cwltool
2727
# `[[` conditional expressions.
2828
PYSOURCES=$(wildcard ${MODULE}/**.py tests/*.py) setup.py
2929
DEVPKGS=pep8 diff_cover autopep8 pylint coverage pydocstyle flake8 pytest isort mock
30-
DEBDEVPKGS=pep8 python-autopep8 pylint python-coverage pydocstyle sloccount python-flake8 python-mock
30+
DEBDEVPKGS=pep8 python-autopep8 pylint python-coverage pydocstyle sloccount \
31+
python-flake8 python-mock shellcheck
3132
VERSION=1.0.$(shell date +%Y%m%d%H%M%S --date=`git log --first-parent \
3233
--max-count=1 --format=format:%cI`)
3334
mkfile_dir := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))

README.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,16 @@ or
139139
140140
cwltool --user-space-docker-cmd=dx-docker https://raw.githubusercontent.com/common-workflow-language/common-workflow-language/master/v1.0/v1.0/test-cwl-out2.cwl https://github.com/common-workflow-language/common-workflow-language/blob/master/v1.0/v1.0/empty.json
141141
142+
``cwltool`` can use `Singularity <http://singularity.lbl.gov/>`_ as a Docker container runtime, an experimental feature.
143+
Singularity will run software containers specified in ``DockerRequirement`` and therefore works with Docker images only,
144+
native Singularity images are not supported.
145+
To use Singularity as the Docker container runtime, provide ``--singularity`` command line option to ``cwltool``.
146+
147+
148+
.. code:: bash
149+
150+
cwltool --singularity https://raw.githubusercontent.com/common-workflow-language/common-workflow-language/master/v1.0/v1.0/v1.0/cat3-tool-mediumcut.cwl https://github.com/common-workflow-language/common-workflow-language/blob/master/v1.0/v1.0/cat-job.json
151+
142152
Tool or workflow loading from remote or local locations
143153
-------------------------------------------------------
144154

cwltool/argparser.py

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,25 @@
1919

2020

2121
def arg_parser(): # type: () -> argparse.ArgumentParser
22-
parser = argparse.ArgumentParser(description='Reference executor for Common Workflow Language')
22+
parser = argparse.ArgumentParser(
23+
description='Reference executor for Common Workflow Language standards.')
2324
parser.add_argument("--basedir", type=Text)
2425
parser.add_argument("--outdir", type=Text, default=os.path.abspath('.'),
2526
help="Output directory, default current directory")
2627

27-
parser.add_argument("--no-container", action="store_false", default=True,
28-
help="Do not execute jobs in a Docker container, even when specified by the CommandLineTool",
29-
dest="use_container")
3028
parser.add_argument("--parallel", action="store_true", default=False,
3129
help="[experimental] Run jobs in parallel. "
3230
"Does not currently keep track of ResourceRequirements like the number of cores"
3331
"or memory and can overload this system")
34-
parser.add_argument("--preserve-environment", type=Text, action="append",
35-
help="Preserve specific environment variable when running CommandLineTools. May be provided multiple times.",
36-
metavar="ENVVAR",
37-
default=["PATH"],
32+
envgroup = parser.add_mutually_exclusive_group()
33+
envgroup.add_argument("--preserve-environment", type=Text, action="append",
34+
help="Preserve specific environment variable when "
35+
"running CommandLineTools. May be provided multiple "
36+
"times.", metavar="ENVVAR", default=["PATH"],
3837
dest="preserve_environment")
39-
40-
parser.add_argument("--preserve-entire-environment", action="store_true",
41-
help="Preserve entire parent environment when running CommandLineTools.",
42-
default=False,
38+
envgroup.add_argument("--preserve-entire-environment", action="store_true",
39+
help="Preserve all environment variable when running "
40+
"CommandLineTools.", default=False,
4341
dest="preserve_entire_environment")
4442

4543
exgroup = parser.add_mutually_exclusive_group()
@@ -154,10 +152,22 @@ def arg_parser(): # type: () -> argparse.ArgumentParser
154152
"timestamps to the errors, warnings, and "
155153
"notifications.")
156154
parser.add_argument("--js-console", action="store_true", help="Enable javascript console output")
157-
parser.add_argument("--user-space-docker-cmd",
155+
dockergroup = parser.add_mutually_exclusive_group()
156+
dockergroup.add_argument("--user-space-docker-cmd", metavar="CMD",
158157
help="(Linux/OS X only) Specify a user space docker "
159158
"command (like udocker or dx-docker) that will be "
160159
"used to call 'pull' and 'run'")
160+
dockergroup.add_argument("--singularity", action="store_true",
161+
default=False, help="[experimental] Use "
162+
"Singularity runtime for running containers. "
163+
"Requires Singularity v2.3.2+ and Linux with kernel "
164+
"version v3.18+ or with overlayfs support "
165+
"backported.")
166+
dockergroup.add_argument("--no-container", action="store_false",
167+
default=True, help="Do not execute jobs in a "
168+
"Docker container, even when `DockerRequirement` "
169+
"is specified under `hints`.",
170+
dest="use_container")
161171

162172
dependency_resolvers_configuration_help = argparse.SUPPRESS
163173
dependencies_directory_help = argparse.SUPPRESS
@@ -237,8 +247,15 @@ def arg_parser(): # type: () -> argparse.ArgumentParser
237247
parser.add_argument("--overrides", type=str,
238248
default=None, help="Read process requirement overrides from file.")
239249

240-
parser.add_argument("workflow", type=Text, nargs="?", default=None)
241-
parser.add_argument("job_order", nargs=argparse.REMAINDER)
250+
parser.add_argument("workflow", type=Text, nargs="?", default=None,
251+
metavar='cwl_document', help="path or URL to a CWL Workflow, "
252+
"CommandLineTool, or ExpressionTool. If the `inputs_object` has a "
253+
"`cwl:tool` field indicating the path or URL to the cwl_document, "
254+
" then the `workflow` argument is optional.")
255+
parser.add_argument("job_order", nargs=argparse.REMAINDER,
256+
metavar='inputs_object', help="path or URL to a YAML or JSON "
257+
"formatted description of the required input values for the given "
258+
"`cwl_document`.")
242259

243260
return parser
244261

cwltool/builder.py

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def bind_input(self, schema, datum, lead_pos=None, tail_pos=None):
7878
lead_pos = []
7979
bindings = [] # type: List[Dict[Text,Text]]
8080
binding = None # type: Dict[Text,Any]
81+
value_from_expression = False
8182
if "inputBinding" in schema and isinstance(schema["inputBinding"], dict):
8283
binding = copy.copy(schema["inputBinding"])
8384

@@ -87,29 +88,33 @@ def bind_input(self, schema, datum, lead_pos=None, tail_pos=None):
8788
binding["position"] = aslist(lead_pos) + [0] + aslist(tail_pos)
8889

8990
binding["datum"] = datum
91+
if "valueFrom" in binding:
92+
value_from_expression = True
9093

9194
# Handle union types
9295
if isinstance(schema["type"], list):
93-
for t in schema["type"]:
94-
if isinstance(t, (str, Text)) and self.names.has_name(t, ""):
95-
avsc = self.names.get_name(t, "")
96-
elif isinstance(t, dict) and "name" in t and self.names.has_name(t["name"], ""):
97-
avsc = self.names.get_name(t["name"], "")
98-
else:
99-
avsc = AvroSchemaFromJSONData(t, self.names)
100-
if validate.validate(avsc, datum):
101-
schema = copy.deepcopy(schema)
102-
schema["type"] = t
103-
return self.bind_input(schema, datum, lead_pos=lead_pos, tail_pos=tail_pos)
104-
raise validate.ValidationException(u"'%s' is not a valid union %s" % (datum, schema["type"]))
96+
if not value_from_expression:
97+
for t in schema["type"]:
98+
if isinstance(t, (str, Text)) and self.names.has_name(t, ""):
99+
avsc = self.names.get_name(t, "")
100+
elif isinstance(t, dict) and "name" in t and self.names.has_name(t["name"], ""):
101+
avsc = self.names.get_name(t["name"], "")
102+
else:
103+
avsc = AvroSchemaFromJSONData(t, self.names)
104+
if validate.validate(avsc, datum):
105+
schema = copy.deepcopy(schema)
106+
schema["type"] = t
107+
return self.bind_input(schema, datum, lead_pos=lead_pos, tail_pos=tail_pos)
108+
raise validate.ValidationException(u"'%s' is not a valid union %s" % (datum, schema["type"]))
105109
elif isinstance(schema["type"], dict):
106-
st = copy.deepcopy(schema["type"])
107-
if binding and "inputBinding" not in st and st["type"] == "array" and "itemSeparator" not in binding:
108-
st["inputBinding"] = {}
109-
for k in ("secondaryFiles", "format", "streamable"):
110-
if k in schema:
111-
st[k] = schema[k]
112-
bindings.extend(self.bind_input(st, datum, lead_pos=lead_pos, tail_pos=tail_pos))
110+
if not value_from_expression:
111+
st = copy.deepcopy(schema["type"])
112+
if binding and "inputBinding" not in st and st["type"] == "array" and "itemSeparator" not in binding:
113+
st["inputBinding"] = {}
114+
for k in ("secondaryFiles", "format", "streamable"):
115+
if k in schema:
116+
st[k] = schema[k]
117+
bindings.extend(self.bind_input(st, datum, lead_pos=lead_pos, tail_pos=tail_pos))
113118
else:
114119
if schema["type"] in self.schemaDefs:
115120
schema = self.schemaDefs[schema["type"]]
@@ -212,15 +217,18 @@ def generate_arg(self, binding): # type: (Dict[Text,Any]) -> List[Text]
212217

213218
prefix = binding.get("prefix")
214219
sep = binding.get("separate", True)
220+
if prefix is None and not sep:
221+
with SourceLine(binding, "separate", WorkflowException, _logger.isEnabledFor(logging.DEBUG)):
222+
raise WorkflowException("'separate' option can not be specified without prefix")
215223

216224
l = [] # type: List[Dict[Text,Text]]
217225
if isinstance(value, list):
218-
if binding.get("itemSeparator"):
226+
if binding.get("itemSeparator") and value:
219227
l = [binding["itemSeparator"].join([self.tostr(v) for v in value])]
220228
elif binding.get("valueFrom"):
221229
value = [self.tostr(v) for v in value]
222230
return ([prefix] if prefix else []) + value
223-
elif prefix:
231+
elif prefix and value:
224232
return [prefix]
225233
else:
226234
return []
@@ -244,8 +252,8 @@ def generate_arg(self, binding): # type: (Dict[Text,Any]) -> List[Text]
244252

245253
return [a for a in args if a is not None]
246254

247-
def do_eval(self, ex, context=None, pull_image=True, recursive=False):
248-
# type: (Union[Dict[Text, Text], Text], Any, bool, bool) -> Any
255+
def do_eval(self, ex, context=None, pull_image=True, recursive=False, strip_whitespace=True):
256+
# type: (Union[Dict[Text, Text], Text], Any, bool, bool, bool) -> Any
249257
if recursive:
250258
if isinstance(ex, dict):
251259
return {k: self.do_eval(v, context, pull_image, recursive) for k, v in iteritems(ex)}
@@ -260,4 +268,5 @@ def do_eval(self, ex, context=None, pull_image=True, recursive=False):
260268
timeout=self.timeout,
261269
debug=self.debug,
262270
js_console=self.js_console,
263-
force_docker_pull=self.force_docker_pull)
271+
force_docker_pull=self.force_docker_pull,
272+
strip_whitespace=strip_whitespace)

0 commit comments

Comments
 (0)