Skip to content

Commit fab8a5e

Browse files
2 parents c4b7e5c + 972b0a6 commit fab8a5e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1483
-281
lines changed

.github/ISSUE_TEMPLATE.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
This issue tracker is for reporting bugs in cwltool (the CWL reference implementation) itself. Please use the [Gitter channel](https://gitter.im/common-workflow-language/common-workflow-language) or [Biostars forum](https://www.biostars.org/) for general questions about using cwltool to create/run workflows or issues not related to cwltool. Don't forget to use cwl tag when posting on Biostars Forum.
2+
3+
If you'd like to report a bug, fill out the template below and provide any extra information that may be useful / related to your problem. Ideally, you create an [a Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) reproducing the problem before opening an issue to ensure it's not caused by something in your code.
4+
5+
Before you submit, please delete this help text above the ---
6+
Thanks!
7+
---
8+
9+
## Expected Behavior
10+
Tell us what should happen
11+
12+
## Actual Behavior
13+
Tell us what happens instead
14+
15+
## Workflow Code
16+
```
17+
Paste the template code (ideally a minimal example) that causes the issue
18+
19+
```
20+
21+
## Full Traceback
22+
```pytb
23+
Paste the full traceback in case there is an exception
24+
Run the workflow with ``--debug`` flag for more verbose logging
25+
```
26+
27+
## Your Environment
28+
* cwltool version:
29+
Check using ``cwltool --version``

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
include gittaggers.py Makefile cwltool.py
22
include tests/*
3+
include tests/tmp1/tmp2/tmp3/.gitkeep
34
include tests/wf/*
45
include cwltool/schemas/v1.0/*.yml
56
include cwltool/schemas/draft-2/*.yml

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ MODULE=cwltool
2626
# `SHELL=bash` doesn't work for some, so don't use BASH-isms like
2727
# `[[` conditional expressions.
2828
PYSOURCES=$(wildcard ${MODULE}/**.py tests/*.py) setup.py
29-
DEVPKGS=pep8 diff_cover autopep8 pylint coverage pep257 flake8 pytest
29+
DEVPKGS=pep8 diff_cover autopep8 pylint coverage pep257 flake8 pytest isort
3030
DEBDEVPKGS=pep8 python-autopep8 pylint python-coverage pep257 sloccount python-flake8
3131
VERSION=1.0.$(shell date +%Y%m%d%H%M%S --date=`git log --first-parent \
3232
--max-count=1 --format=format:%cI`)
@@ -65,6 +65,11 @@ clean: FORCE
6565
rm -Rf .coverage
6666
rm -f diff-cover.html
6767

68+
# Linting and code style related targets
69+
## sorting imports using isort: https://github.com/timothycrosley/isort
70+
sort_imports:
71+
isort ${MODULE}/*.py tests/*.py setup.py
72+
6873
## pep8 : check Python code style
6974
pep8: $(PYSOURCES)
7075
pep8 --exclude=_version.py --show-source --show-pep8 $^ || true

cwltool/builder.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import copy
2+
from typing import Any, Callable, Text, Type, Union
3+
4+
from six import iteritems, string_types
25

36
import avro
47
import schema_salad.validate as validate
58
from schema_salad.sourceline import SourceLine
6-
from typing import Any, Callable, Text, Type, Union
7-
from six import string_types, iteritems
89

910
from . import expression
1011
from .errors import WorkflowException
11-
from .pathmapper import PathMapper, normalizeFilesDirs, get_listing, visit_class
12+
from .mutation import MutationManager
13+
from .pathmapper import (PathMapper, get_listing, normalizeFilesDirs,
14+
visit_class)
1215
from .stdfsaccess import StdFsAccess
1316
from .utils import aslist
1417

@@ -41,6 +44,7 @@ def __init__(self): # type: () -> None
4144
self.make_fs_access = None # type: Type[StdFsAccess]
4245
self.build_job_script = None # type: Callable[[List[str]], Text]
4346
self.debug = False # type: bool
47+
self.mutation_manager = None # type: MutationManager
4448

4549
# One of "no_listing", "shallow_listing", "deep_listing"
4650
# Will be default "no_listing" for CWL v1.1

cwltool/cwlrdf.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
from typing import IO, Any, Dict, Text
2+
13
from rdflib import Graph
4+
25
from schema_salad.jsonld_context import makerdf
36
from schema_salad.ref_resolver import ContextType
4-
from typing import Any, Dict, IO, Text
57
from six.moves import urllib
68

79
from .process import Process

cwltool/docker.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
import subprocess
55
import sys
66
import tempfile
7+
from typing import Text
78

89
import requests
9-
from typing import Text
1010

1111
from .errors import WorkflowException
1212

@@ -27,6 +27,17 @@ def get_image(dockerRequirement, pull_image, dry_run=False):
2727
sp = dockerRequirement["dockerImageId"].split(":")
2828
if len(sp) == 1:
2929
sp.append("latest")
30+
elif len(sp) == 2:
31+
# if sp[1] doesn't match valid tag names, it is a part of repository
32+
if not re.match(r'[\w][\w.-]{0,127}', sp[1]):
33+
sp[0] = sp[0] + ":" + sp[1]
34+
sp[1] = "latest"
35+
elif len(sp) == 3:
36+
if re.match(r'[\w][\w.-]{0,127}', sp[2]):
37+
sp[0] = sp[0] + ":" + sp[1]
38+
sp[1] = sp[2]
39+
del sp[2]
40+
3041
# check for repository:tag match or image id match
3142
if ((sp[0] == m.group(1) and sp[1] == m.group(2)) or dockerRequirement["dockerImageId"] == m.group(3)):
3243
found = True

cwltool/docker_uid.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import print_function
2-
import subprocess
32

3+
import subprocess
44
from typing import Text
55

66

cwltool/draft2tool.py

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,33 @@
66
import re
77
import shutil
88
import tempfile
9-
from six.moves import urllib
10-
from six import string_types, u
119
from functools import partial
10+
from typing import Any, Callable, Dict, Generator, Optional, Text, Union, cast
11+
12+
from six import string_types, u
1213

1314
import schema_salad.validate as validate
1415
import shellescape
1516
from schema_salad.ref_resolver import file_uri, uri_file_path
1617
from schema_salad.sourceline import SourceLine, indent
17-
from typing import Any, Callable, cast, Generator, Text, Union
18+
from six.moves import urllib
1819

19-
from .builder import CONTENT_LIMIT, substitute, Builder
20-
from .pathmapper import adjustFileObjs, adjustDirObjs, visit_class
20+
from .builder import CONTENT_LIMIT, Builder, substitute
2121
from .errors import WorkflowException
22-
from .job import CommandLineJob
23-
from .pathmapper import PathMapper, get_listing, trim_listing
24-
from .process import Process, shortname, uniquename, normalizeFilesDirs, compute_checksums
22+
from .flatten import flatten
23+
from .job import CommandLineJob, DockerCommandLineJob, JobBase
24+
from .pathmapper import (PathMapper, adjustDirObjs, adjustFileObjs,
25+
get_listing, trim_listing, visit_class)
26+
from .process import (Process, UnsupportedRequirement,
27+
_logger_validation_warnings, compute_checksums,
28+
normalizeFilesDirs, shortname, uniquename)
2529
from .stdfsaccess import StdFsAccess
2630
from .utils import aslist
2731

2832
ACCEPTLIST_EN_STRICT_RE = re.compile(r"^[a-zA-Z0-9._+-]+$")
2933
ACCEPTLIST_EN_RELAXED_RE = re.compile(r".*") # Accept anything
3034
ACCEPTLIST_RE = ACCEPTLIST_EN_STRICT_RE
3135

32-
from .flatten import flatten
3336

3437
_logger = logging.getLogger("cwltool")
3538

@@ -150,6 +153,7 @@ def run(self, **kwargs):
150153
# walk over input as implicit reassignment doesn't reach everything in builder.bindings
151154
def check_adjust(builder, f):
152155
# type: (Builder, Dict[Text, Any]) -> Dict[Text, Any]
156+
153157
f["path"] = builder.pathmapper.mapper(f["location"])[1]
154158
f["dirname"], f["basename"] = os.path.split(f["path"])
155159
if f["class"] == "File":
@@ -171,20 +175,28 @@ def __init__(self, toolpath_object, **kwargs):
171175
# type: (Dict[Text, Any], **Any) -> None
172176
super(CommandLineTool, self).__init__(toolpath_object, **kwargs)
173177

174-
def makeJobRunner(self): # type: () -> CommandLineJob
175-
return CommandLineJob()
178+
def makeJobRunner(self, use_container=True): # type: (Optional[bool]) -> JobBase
179+
dockerReq, _ = self.get_requirement("DockerRequirement")
180+
if dockerReq and use_container:
181+
return DockerCommandLineJob()
182+
else:
183+
for t in reversed(self.requirements):
184+
if t["class"] == "DockerRequirement":
185+
raise UnsupportedRequirement(
186+
"--no-container, but this CommandLineTool has "
187+
"DockerRequirement under 'requirements'.")
188+
return CommandLineJob()
176189

177190
def makePathMapper(self, reffiles, stagedir, **kwargs):
178191
# type: (List[Any], Text, **Any) -> PathMapper
179-
dockerReq, _ = self.get_requirement("DockerRequirement")
180192
return PathMapper(reffiles, kwargs["basedir"], stagedir)
181193

182194
def job(self,
183195
job_order, # type: Dict[Text, Text]
184196
output_callbacks, # type: Callable[[Any, Any], Any]
185197
**kwargs # type: Any
186198
):
187-
# type: (...) -> Generator[Union[CommandLineJob, CallbackJob], None, None]
199+
# type: (...) -> Generator[Union[JobBase, CallbackJob], None, None]
188200

189201
jobname = uniquename(kwargs.get("name", shortname(self.tool.get("id", "job"))))
190202

@@ -199,9 +211,9 @@ def job(self,
199211
cachebuilder.stagedir,
200212
separateDirs=False)
201213
_check_adjust = partial(check_adjust, cachebuilder)
202-
203214
visit_class([cachebuilder.files, cachebuilder.bindings],
204215
("File", "Directory"), _check_adjust)
216+
205217
cmdline = flatten(map(cachebuilder.generate_arg, cachebuilder.bindings))
206218
(docker_req, docker_is_req) = self.get_requirement("DockerRequirement")
207219
if docker_req and kwargs.get("use_container") is not False:
@@ -264,7 +276,7 @@ def rm_pending_output_callback(output_callbacks, jobcachepending,
264276

265277
reffiles = copy.deepcopy(builder.files)
266278

267-
j = self.makeJobRunner()
279+
j = self.makeJobRunner(kwargs.get("use_container"))
268280
j.builder = builder
269281
j.joborder = builder.job
270282
j.stdin = None
@@ -368,8 +380,39 @@ def rm_pending_output_callback(output_callbacks, jobcachepending,
368380
ls[i] = t["entry"]
369381
j.generatefiles[u"listing"] = ls
370382

383+
inplaceUpdateReq = self.get_requirement("http://commonwl.org/cwltool#InplaceUpdateRequirement")[0]
384+
385+
if inplaceUpdateReq:
386+
j.inplace_update = inplaceUpdateReq["inplaceUpdate"]
371387
normalizeFilesDirs(j.generatefiles)
372388

389+
readers = {}
390+
muts = set()
391+
392+
if builder.mutation_manager:
393+
def register_mut(f):
394+
muts.add(f["location"])
395+
builder.mutation_manager.register_mutation(j.name, f)
396+
397+
def register_reader(f):
398+
if f["location"] not in muts:
399+
builder.mutation_manager.register_reader(j.name, f)
400+
readers[f["location"]] = f
401+
402+
for li in j.generatefiles["listing"]:
403+
li = cast(Dict[Text, Any], li)
404+
if li.get("writable") and j.inplace_update:
405+
adjustFileObjs(li, register_mut)
406+
adjustDirObjs(li, register_mut)
407+
else:
408+
adjustFileObjs(li, register_reader)
409+
adjustDirObjs(li, register_reader)
410+
411+
adjustFileObjs(builder.files, register_reader)
412+
adjustFileObjs(builder.bindings, register_reader)
413+
adjustDirObjs(builder.files, register_reader)
414+
adjustDirObjs(builder.bindings, register_reader)
415+
373416
j.environment = {}
374417
evr = self.get_requirement("EnvVarRequirement")[0]
375418
if evr:
@@ -391,16 +434,17 @@ def rm_pending_output_callback(output_callbacks, jobcachepending,
391434
j.pathmapper = builder.pathmapper
392435
j.collect_outputs = partial(
393436
self.collect_output_ports, self.tool["outputs"], builder,
394-
compute_checksum=kwargs.get("compute_checksum", True))
437+
compute_checksum=kwargs.get("compute_checksum", True),
438+
jobname=jobname,
439+
readers=readers)
395440
j.output_callback = output_callbacks
396441

397442
yield j
398443

399-
def collect_output_ports(self, ports, builder, outdir, compute_checksum=True):
400-
# type: (Set[Dict[Text, Any]], Builder, Text, bool) -> Dict[Text, Union[Text, List[Any], Dict[Text, Any]]]
444+
def collect_output_ports(self, ports, builder, outdir, compute_checksum=True, jobname="", readers=None):
445+
# type: (Set[Dict[Text, Any]], Builder, Text, bool, Text, Dict[Text, Any]) -> Dict[Text, Union[Text, List[Any], Dict[Text, Any]]]
401446
ret = {} # type: Dict[Text, Union[Text, List[Any], Dict[Text, Any]]]
402447
try:
403-
404448
fs_access = builder.make_fs_access(outdir)
405449
custom_output = fs_access.join(outdir, "cwl.output.json")
406450
if fs_access.exists(custom_output):
@@ -429,14 +473,22 @@ def collect_output_ports(self, ports, builder, outdir, compute_checksum=True):
429473
visit_class(ret, ("File", "Directory"), cast(Callable[[Any], Any], revmap))
430474
visit_class(ret, ("File", "Directory"), remove_path)
431475
normalizeFilesDirs(ret)
476+
if builder.mutation_manager:
477+
adjustFileObjs(ret, builder.mutation_manager.set_generation)
432478
visit_class(ret, ("File", "Directory"), partial(check_valid_locations, fs_access))
479+
433480
if compute_checksum:
434481
adjustFileObjs(ret, partial(compute_checksums, fs_access))
435482

436-
validate.validate_ex(self.names.get_name("outputs_record_schema", ""), ret)
483+
validate.validate_ex(self.names.get_name("outputs_record_schema", ""), ret,
484+
strict=False, logger=_logger_validation_warnings)
437485
return ret if ret is not None else {}
438486
except validate.ValidationException as e:
439487
raise WorkflowException("Error validating output record. " + Text(e) + "\n in " + json.dumps(ret, indent=4))
488+
finally:
489+
if builder.mutation_manager and readers:
490+
for r in readers.values():
491+
builder.mutation_manager.release_reader(jobname, r)
440492

441493
def collect_output(self, schema, builder, outdir, fs_access, compute_checksum=True):
442494
# type: (Dict[Text, Any], Builder, Text, StdFsAccess, bool) -> Union[Dict[Text, Any], List[Union[Dict[Text, Any], Text]]]

cwltool/expression.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import json
33
import logging
44
import re
5+
from typing import Any, AnyStr, Dict, List, Text, Union
56

6-
from typing import Any, AnyStr, Union, Text, Dict, List
77
from six import u
88

99
from . import sandboxjs

cwltool/extensions.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,18 @@ $graph:
1919
type:
2020
- type: enum
2121
name: LoadListingEnum
22-
symbols: [no_listing, shallow_listing, deep_listing]
22+
symbols: [no_listing, shallow_listing, deep_listing]
23+
24+
- name: InplaceUpdateRequirement
25+
type: record
26+
inVocab: false
27+
extends: cwl:ProcessRequirement
28+
fields:
29+
class:
30+
type: string
31+
doc: "Always 'InplaceUpdateRequirement'"
32+
jsonldPredicate:
33+
"_id": "@type"
34+
"_type": "@vocab"
35+
inplaceUpdate:
36+
type: boolean

0 commit comments

Comments
 (0)