Skip to content

Commit 91c647e

Browse files
authored
Merge branch 'master' into fix-648
2 parents 1cf9311 + 1124d2d commit 91c647e

32 files changed

+1507
-1484
lines changed

.travis.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
sudo: false
22
language: python
3+
cache: pip
34
python:
45
- 2.7
5-
- 3.3
66
- 3.4
77
- 3.5
88
- 3.6
99
os:
1010
- linux
1111
install:
1212
- pip install tox-travis
13+
jobs:
14+
include:
15+
- stage: release-test
16+
script: RELEASE_SKIP=head ./release-test.sh
1317
script: tox
1418
branches:
1519
only:
1620
- master
1721
notifications:
18-
email: false
22+
email: false

Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ 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
31-
VERSION=1.0.$(shell date +%Y%m%d%H%M%S --date=`git log --first-parent \
30+
DEBDEVPKGS=pep8 python-autopep8 pylint python-coverage pydocstyle sloccount \
31+
python-flake8 python-mock shellcheck
32+
VERSION=1.0.$(shell date +%Y%m%d%H%M%S --utc --date=`git log --first-parent \
3233
--max-count=1 --format=format:%cI`)
3334
mkfile_dir := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
3435

@@ -56,7 +57,7 @@ install: FORCE
5657
dist: dist/${MODULE}-$(VERSION).tar.gz
5758

5859
dist/${MODULE}-$(VERSION).tar.gz: $(SOURCES)
59-
./setup.py sdist
60+
./setup.py sdist bdist_wheel
6061

6162
## clean : clean up all temporary / machine-generated files
6263
clean: FORCE
@@ -189,4 +190,3 @@ FORCE:
189190
# Example `make print-VERSION`
190191
# From https://www.cmcrossroads.com/article/printing-value-makefile-variable
191192
print-% : ; @echo $* = $($*)
192-

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

appveyor.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ environment:
1212
PYTHON_VERSION: "2.7.x"
1313
PYTHON_ARCH: "64"
1414

15-
- PYTHON: "C:\\Python33-x64"
16-
PYTHON_VERSION: "3.3.x"
17-
PYTHON_ARCH: "64"
18-
1915
- PYTHON: "C:\\Python34-x64"
2016
PYTHON_VERSION: "3.4.x"
2117
PYTHON_ARCH: "64"

cwltool/argparser.py

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,37 @@
77

88
from typing import (Any, AnyStr, Dict, List, Sequence, Text, Union, cast)
99

10+
from . import loghandler
1011
from schema_salad.ref_resolver import file_uri
1112
from .process import (Process, shortname)
1213
from .resolver import ga4gh_tool_registries
1314
from .software_requirements import (SOFTWARE_REQUIREMENTS_ENABLED)
1415

1516
_logger = logging.getLogger("cwltool")
1617

17-
defaultStreamHandler = logging.StreamHandler()
18-
_logger.addHandler(defaultStreamHandler)
19-
_logger.setLevel(logging.INFO)
18+
DEFAULT_TMP_PREFIX = "tmp"
2019

2120

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

28-
parser.add_argument("--no-container", action="store_false", default=True,
29-
help="Do not execute jobs in a Docker container, even when specified by the CommandLineTool",
30-
dest="use_container")
3128
parser.add_argument("--parallel", action="store_true", default=False,
3229
help="[experimental] Run jobs in parallel. "
3330
"Does not currently keep track of ResourceRequirements like the number of cores"
3431
"or memory and can overload this system")
35-
parser.add_argument("--preserve-environment", type=Text, action="append",
36-
help="Preserve specific environment variable when running CommandLineTools. May be provided multiple times.",
37-
metavar="ENVVAR",
38-
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"],
3937
dest="preserve_environment")
40-
41-
parser.add_argument("--preserve-entire-environment", action="store_true",
42-
help="Preserve entire parent environment when running CommandLineTools.",
43-
default=False,
38+
envgroup.add_argument("--preserve-entire-environment", action="store_true",
39+
help="Preserve all environment variable when running "
40+
"CommandLineTools.", default=False,
4441
dest="preserve_entire_environment")
4542

4643
exgroup = parser.add_mutually_exclusive_group()
@@ -75,12 +72,12 @@ def arg_parser(): # type: () -> argparse.ArgumentParser
7572

7673
parser.add_argument("--tmpdir-prefix", type=Text,
7774
help="Path prefix for temporary directories",
78-
default="tmp")
75+
default=DEFAULT_TMP_PREFIX)
7976

8077
exgroup = parser.add_mutually_exclusive_group()
8178
exgroup.add_argument("--tmp-outdir-prefix", type=Text,
8279
help="Path prefix for intermediate output directories",
83-
default="tmp")
80+
default=DEFAULT_TMP_PREFIX)
8481

8582
exgroup.add_argument("--cachedir", type=Text, default="",
8683
help="Directory to cache intermediate workflow outputs to avoid recomputing steps.")
@@ -155,10 +152,22 @@ def arg_parser(): # type: () -> argparse.ArgumentParser
155152
"timestamps to the errors, warnings, and "
156153
"notifications.")
157154
parser.add_argument("--js-console", action="store_true", help="Enable javascript console output")
158-
parser.add_argument("--user-space-docker-cmd",
155+
dockergroup = parser.add_mutually_exclusive_group()
156+
dockergroup.add_argument("--user-space-docker-cmd", metavar="CMD",
159157
help="(Linux/OS X only) Specify a user space docker "
160158
"command (like udocker or dx-docker) that will be "
161159
"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")
162171

163172
dependency_resolvers_configuration_help = argparse.SUPPRESS
164173
dependencies_directory_help = argparse.SUPPRESS
@@ -193,7 +202,7 @@ def arg_parser(): # type: () -> argparse.ArgumentParser
193202
parser.add_argument("--default-container",
194203
help="Specify a default docker container that will be used if the workflow fails to specify one.")
195204
parser.add_argument("--no-match-user", action="store_true",
196-
help="Disable passing the current uid to 'docker run --user`")
205+
help="Disable passing the current uid to `docker run --user`")
197206
parser.add_argument("--disable-net", action="store_true",
198207
help="Use docker's default networking for containers;"
199208
" the default is to enable networking.")
@@ -238,8 +247,15 @@ def arg_parser(): # type: () -> argparse.ArgumentParser
238247
parser.add_argument("--overrides", type=str,
239248
default=None, help="Read process requirement overrides from file.")
240249

241-
parser.add_argument("workflow", type=Text, nargs="?", default=None)
242-
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`.")
243259

244260
return parser
245261

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)