Skip to content

Commit 8ff32c3

Browse files
authored
Merge branch 'master' into node_fix
2 parents a8ca7d4 + b049635 commit 8ff32c3

33 files changed

+1357
-265
lines changed

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

cwltool/builder.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88

99
from . import expression
1010
from .errors import WorkflowException
11-
from .pathmapper import PathMapper, adjustFileObjs, normalizeFilesDirs, get_listing
11+
from .pathmapper import PathMapper, normalizeFilesDirs, get_listing, visit_class
1212
from .stdfsaccess import StdFsAccess
1313
from .utils import aslist
14+
from .mutation import MutationManager
1415

1516
CONTENT_LIMIT = 64 * 1024
1617

@@ -41,6 +42,7 @@ def __init__(self): # type: () -> None
4142
self.make_fs_access = None # type: Type[StdFsAccess]
4243
self.build_job_script = None # type: Callable[[List[str]], Text]
4344
self.debug = False # type: bool
45+
self.mutation_manager = None # type: MutationManager
4446

4547
# One of "no_listing", "shallow_listing", "deep_listing"
4648
# Will be default "no_listing" for CWL v1.1
@@ -144,7 +146,7 @@ def _capture_files(f):
144146
self.files.append(f)
145147
return f
146148

147-
adjustFileObjs(datum.get("secondaryFiles", []), _capture_files)
149+
visit_class(datum.get("secondaryFiles", []), ("File", "Directory"), _capture_files)
148150

149151
if schema["type"] == "Directory":
150152
ll = self.loadListing or (binding and binding.get("loadListing"))

cwltool/draft2tool.py

Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
import shellescape
1515
from schema_salad.ref_resolver import file_uri, uri_file_path
1616
from schema_salad.sourceline import SourceLine, indent
17-
from typing import Any, Callable, cast, Generator, Text, Union
17+
from typing import Any, Callable, cast, Generator, Text, Union, Dict
1818

19-
from .builder import CONTENT_LIMIT, substitute, Builder, adjustFileObjs
20-
from .pathmapper import adjustDirObjs
19+
from .builder import CONTENT_LIMIT, substitute, Builder
20+
from .pathmapper import adjustFileObjs, adjustDirObjs, visit_class
2121
from .errors import WorkflowException
22-
from .job import CommandLineJob
22+
from .job import JobBase, CommandLineJob, DockerCommandLineJob
2323
from .pathmapper import PathMapper, get_listing, trim_listing
24-
from .process import Process, shortname, uniquename, normalizeFilesDirs, compute_checksums
24+
from .process import Process, shortname, uniquename, normalizeFilesDirs, compute_checksums, _logger_validation_warnings
2525
from .stdfsaccess import StdFsAccess
2626
from .utils import aslist
2727

@@ -106,6 +106,8 @@ def revmap_file(builder, outdir, f):
106106
revmap_f = builder.pathmapper.reversemap(path)
107107
if revmap_f:
108108
f["location"] = revmap_f[1]
109+
elif path == builder.outdir:
110+
f["location"] = outdir
109111
elif path.startswith(builder.outdir):
110112
f["location"] = builder.fs_access.join(outdir, path[len(builder.outdir) + 1:])
111113
return f
@@ -148,6 +150,7 @@ def run(self, **kwargs):
148150
# walk over input as implicit reassignment doesn't reach everything in builder.bindings
149151
def check_adjust(builder, f):
150152
# type: (Builder, Dict[Text, Any]) -> Dict[Text, Any]
153+
151154
f["path"] = builder.pathmapper.mapper(f["location"])[1]
152155
f["dirname"], f["basename"] = os.path.split(f["path"])
153156
if f["class"] == "File":
@@ -156,26 +159,36 @@ def check_adjust(builder, f):
156159
raise WorkflowException("Invalid filename: '%s' contains illegal characters" % (f["basename"]))
157160
return f
158161

162+
def check_valid_locations(fs_access, ob):
163+
if ob["location"].startswith("_:"):
164+
pass
165+
if ob["class"] == "File" and not fs_access.isfile(ob["location"]):
166+
raise validate.ValidationException("Does not exist or is not a File: '%s'" % ob["location"])
167+
if ob["class"] == "Directory" and not fs_access.isdir(ob["location"]):
168+
raise validate.ValidationException("Does not exist or is not a Directory: '%s'" % ob["location"])
159169

160170
class CommandLineTool(Process):
161171
def __init__(self, toolpath_object, **kwargs):
162172
# type: (Dict[Text, Any], **Any) -> None
163173
super(CommandLineTool, self).__init__(toolpath_object, **kwargs)
164174

165-
def makeJobRunner(self): # type: () -> CommandLineJob
166-
return CommandLineJob()
175+
def makeJobRunner(self): # type: () -> JobBase
176+
dockerReq, _ = self.get_requirement("DockerRequirement")
177+
if dockerReq:
178+
return DockerCommandLineJob()
179+
else:
180+
return CommandLineJob()
167181

168182
def makePathMapper(self, reffiles, stagedir, **kwargs):
169183
# type: (List[Any], Text, **Any) -> PathMapper
170-
dockerReq, _ = self.get_requirement("DockerRequirement")
171184
return PathMapper(reffiles, kwargs["basedir"], stagedir)
172185

173186
def job(self,
174187
job_order, # type: Dict[Text, Text]
175188
output_callbacks, # type: Callable[[Any, Any], Any]
176189
**kwargs # type: Any
177190
):
178-
# type: (...) -> Generator[Union[CommandLineJob, CallbackJob], None, None]
191+
# type: (...) -> Generator[Union[JobBase, CallbackJob], None, None]
179192

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

@@ -190,10 +203,9 @@ def job(self,
190203
cachebuilder.stagedir,
191204
separateDirs=False)
192205
_check_adjust = partial(check_adjust, cachebuilder)
193-
adjustFileObjs(cachebuilder.files, _check_adjust)
194-
adjustFileObjs(cachebuilder.bindings, _check_adjust)
195-
adjustDirObjs(cachebuilder.files, _check_adjust)
196-
adjustDirObjs(cachebuilder.bindings, _check_adjust)
206+
visit_class([cachebuilder.files, cachebuilder.bindings],
207+
("File", "Directory"), _check_adjust)
208+
197209
cmdline = flatten(map(cachebuilder.generate_arg, cachebuilder.bindings))
198210
(docker_req, docker_is_req) = self.get_requirement("DockerRequirement")
199211
if docker_req and kwargs.get("use_container") is not False:
@@ -290,10 +302,7 @@ def rm_pending_output_callback(output_callbacks, jobcachepending,
290302

291303
_check_adjust = partial(check_adjust, builder)
292304

293-
adjustFileObjs(builder.files, _check_adjust)
294-
adjustFileObjs(builder.bindings, _check_adjust)
295-
adjustDirObjs(builder.files, _check_adjust)
296-
adjustDirObjs(builder.bindings, _check_adjust)
305+
visit_class([builder.files, builder.bindings], ("File", "Directory"), _check_adjust)
297306

298307
if self.tool.get("stdin"):
299308
with SourceLine(self.tool, "stdin", validate.ValidationException):
@@ -363,8 +372,39 @@ def rm_pending_output_callback(output_callbacks, jobcachepending,
363372
ls[i] = t["entry"]
364373
j.generatefiles[u"listing"] = ls
365374

375+
inplaceUpdateReq = self.get_requirement("http://commonwl.org/cwltool#InplaceUpdateRequirement")[0]
376+
377+
if inplaceUpdateReq:
378+
j.inplace_update = inplaceUpdateReq["inplaceUpdate"]
366379
normalizeFilesDirs(j.generatefiles)
367380

381+
readers = {}
382+
muts = set()
383+
384+
if builder.mutation_manager:
385+
def register_mut(f):
386+
muts.add(f["location"])
387+
builder.mutation_manager.register_mutation(j.name, f)
388+
389+
def register_reader(f):
390+
if f["location"] not in muts:
391+
builder.mutation_manager.register_reader(j.name, f)
392+
readers[f["location"]] = f
393+
394+
for li in j.generatefiles["listing"]:
395+
li = cast(Dict[Text, Any], li)
396+
if li.get("writable") and j.inplace_update:
397+
adjustFileObjs(li, register_mut)
398+
adjustDirObjs(li, register_mut)
399+
else:
400+
adjustFileObjs(li, register_reader)
401+
adjustDirObjs(li, register_reader)
402+
403+
adjustFileObjs(builder.files, register_reader)
404+
adjustFileObjs(builder.bindings, register_reader)
405+
adjustDirObjs(builder.files, register_reader)
406+
adjustDirObjs(builder.bindings, register_reader)
407+
368408
j.environment = {}
369409
evr = self.get_requirement("EnvVarRequirement")[0]
370410
if evr:
@@ -386,16 +426,17 @@ def rm_pending_output_callback(output_callbacks, jobcachepending,
386426
j.pathmapper = builder.pathmapper
387427
j.collect_outputs = partial(
388428
self.collect_output_ports, self.tool["outputs"], builder,
389-
compute_checksum=kwargs.get("compute_checksum", True))
429+
compute_checksum=kwargs.get("compute_checksum", True),
430+
jobname=jobname,
431+
readers=readers)
390432
j.output_callback = output_callbacks
391433

392434
yield j
393435

394-
def collect_output_ports(self, ports, builder, outdir, compute_checksum=True):
395-
# type: (Set[Dict[Text, Any]], Builder, Text, bool) -> Dict[Text, Union[Text, List[Any], Dict[Text, Any]]]
436+
def collect_output_ports(self, ports, builder, outdir, compute_checksum=True, jobname="", readers=None):
437+
# type: (Set[Dict[Text, Any]], Builder, Text, bool, Text, Dict[Text, Any]) -> Dict[Text, Union[Text, List[Any], Dict[Text, Any]]]
396438
ret = {} # type: Dict[Text, Union[Text, List[Any], Dict[Text, Any]]]
397439
try:
398-
399440
fs_access = builder.make_fs_access(outdir)
400441
custom_output = fs_access.join(outdir, "cwl.output.json")
401442
if fs_access.exists(custom_output):
@@ -419,21 +460,27 @@ def collect_output_ports(self, ports, builder, outdir, compute_checksum=True):
419460
% (shortname(port["id"]), indent(u(str(e)))))
420461

421462
if ret:
463+
revmap = partial(revmap_file, builder, outdir)
422464
adjustDirObjs(ret, trim_listing)
423-
adjustFileObjs(ret,
424-
cast(Callable[[Any], Any], # known bug in mypy
425-
# https://github.com/python/mypy/issues/797
426-
partial(revmap_file, builder, outdir)))
427-
adjustFileObjs(ret, remove_path)
428-
adjustDirObjs(ret, remove_path)
465+
visit_class(ret, ("File", "Directory"), cast(Callable[[Any], Any], revmap))
466+
visit_class(ret, ("File", "Directory"), remove_path)
429467
normalizeFilesDirs(ret)
468+
if builder.mutation_manager:
469+
adjustFileObjs(ret, builder.mutation_manager.set_generation)
470+
visit_class(ret, ("File", "Directory"), partial(check_valid_locations, fs_access))
471+
430472
if compute_checksum:
431473
adjustFileObjs(ret, partial(compute_checksums, fs_access))
432474

433-
validate.validate_ex(self.names.get_name("outputs_record_schema", ""), ret)
475+
validate.validate_ex(self.names.get_name("outputs_record_schema", ""), ret,
476+
strict=False, logger=_logger_validation_warnings)
434477
return ret if ret is not None else {}
435478
except validate.ValidationException as e:
436-
raise WorkflowException("Error validating output record, " + Text(e) + "\n in " + json.dumps(ret, indent=4))
479+
raise WorkflowException("Error validating output record. " + Text(e) + "\n in " + json.dumps(ret, indent=4))
480+
finally:
481+
if builder.mutation_manager and readers:
482+
for r in readers.values():
483+
builder.mutation_manager.release_reader(jobname, r)
437484

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

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)