Skip to content

Commit 663f1aa

Browse files
authored
Merge branch 'master' into udocker
2 parents 5146ac9 + 5adb3fb commit 663f1aa

File tree

10 files changed

+186
-22
lines changed

10 files changed

+186
-22
lines changed

cwltool/load_tool.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,13 @@ def make_tool(document_loader, # type: Loader
281281
"""Make a Python CWL object."""
282282
resolveduri = document_loader.resolve_ref(uri)[0]
283283

284+
processobj = None
284285
if isinstance(resolveduri, list):
285-
if len(resolveduri) == 1:
286-
processobj = resolveduri[0]
287-
else:
286+
for obj in resolveduri:
287+
if obj['id'].endswith('#main'):
288+
processobj = obj
289+
break
290+
if not processobj:
288291
raise WorkflowException(
289292
u"Tool file contains graph of multiple objects, must specify "
290293
"one of #%s" % ", #".join(

cwltool/main.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,8 @@ def main(argsl=None, # type: List[str]
790790
'enable_ga4gh_tool_registry': False,
791791
'ga4gh_tool_registries': [],
792792
'find_default_container': None,
793-
'make_template': False
793+
'make_template': False,
794+
'overrides': None
794795
}):
795796
if not hasattr(args, k):
796797
setattr(args, k, v)
@@ -869,10 +870,6 @@ def main(argsl=None, # type: List[str]
869870
skip_schemas=args.skip_schemas,
870871
overrides=overrides)
871872

872-
if args.pack:
873-
stdout.write(print_pack(document_loader, processobj, uri, metadata))
874-
return 0
875-
876873
if args.print_pre:
877874
stdout.write(json.dumps(processobj, indent=4))
878875
return 0
@@ -901,6 +898,11 @@ def main(argsl=None, # type: List[str]
901898
return 0
902899

903900
if args.validate:
901+
_logger.info("Tool definition is valid")
902+
return 0
903+
904+
if args.pack:
905+
stdout.write(print_pack(document_loader, processobj, uri, metadata))
904906
return 0
905907

906908
if args.print_rdf:

cwltool/pack.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from __future__ import absolute_import
22
import copy
3+
import re
34
from typing import Any, Callable, Dict, List, Set, Text, Union, cast
45

5-
from schema_salad.ref_resolver import Loader
6+
from schema_salad.ref_resolver import Loader, SubLoader
67
from six.moves import urllib
8+
from ruamel.yaml.comments import CommentedSeq, CommentedMap
79

810
from .process import shortname, uniquename
911
import six
@@ -64,7 +66,12 @@ def replace_refs(d, rewrite, stem, newstem):
6466
if v in rewrite:
6567
d[s] = rewrite[v]
6668
elif v.startswith(stem):
67-
d[s] = newstem + v[len(stem):]
69+
id_ = v[len(stem):]
70+
# prevent appending newstems if tool is already packed
71+
if id_.startswith(newstem.strip("#")):
72+
d[s] = "#" + id_
73+
else:
74+
d[s] = newstem + id_
6875
replace_refs(v, rewrite, stem, newstem)
6976

7077
def import_embed(d, seen):
@@ -84,12 +91,25 @@ def import_embed(d, seen):
8491
seen.add(this)
8592
break
8693

87-
for v in d.values():
88-
import_embed(v, seen)
94+
for k in sorted(d.keys()):
95+
import_embed(d[k], seen)
8996

9097

9198
def pack(document_loader, processobj, uri, metadata):
9299
# type: (Loader, Union[Dict[Text, Any], List[Dict[Text, Any]]], Text, Dict[Text, Text]) -> Dict[Text, Any]
100+
101+
document_loader = SubLoader(document_loader)
102+
document_loader.idx = {}
103+
if isinstance(processobj, dict):
104+
document_loader.idx[processobj["id"]] = CommentedMap(six.iteritems(processobj))
105+
elif isinstance(processobj, list):
106+
path, frag = urllib.parse.urldefrag(uri)
107+
for po in processobj:
108+
if not frag:
109+
if po["id"].endswith("#main"):
110+
uri = po["id"]
111+
document_loader.idx[po["id"]] = CommentedMap(six.iteritems(po))
112+
93113
def loadref(b, u):
94114
# type: (Text, Text) -> Union[Dict, List, Text]
95115
return document_loader.resolve_ref(u, base_url=b)[0]
@@ -111,7 +131,8 @@ def rewrite_id(r, mainuri):
111131
if r == mainuri:
112132
rewrite[r] = "#main"
113133
elif r.startswith(mainuri) and r[len(mainuri)] in ("#", "/"):
114-
pass
134+
path, frag = urllib.parse.urldefrag(r)
135+
rewrite[r] = "#"+frag
115136
else:
116137
path, frag = urllib.parse.urldefrag(r)
117138
if path == mainpath:
@@ -132,6 +153,9 @@ def rewrite_id(r, mainuri):
132153
schemas = set() # type: Set[Text]
133154
for r in sorted(runs):
134155
dcr, metadata = document_loader.resolve_ref(r)
156+
if isinstance(dcr, CommentedSeq):
157+
dcr = dcr[0]
158+
dcr = cast(CommentedMap, dcr)
135159
if not isinstance(dcr, dict):
136160
continue
137161
for doc in (dcr, metadata):

cwltool/workflow.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -625,8 +625,7 @@ def static_checker(workflow_inputs, workflow_outputs, step_inputs, step_outputs)
625625
all_exception_msg = "\n".join(exception_msgs)
626626

627627
if warnings:
628-
_logger.warning("Workflow checker warning:")
629-
_logger.warning(all_warning_msg)
628+
_logger.warning("Workflow checker warning:\n%s" % all_warning_msg)
630629
if exceptions:
631630
raise validate.ValidationException(all_exception_msg)
632631

tests/test_pack.py

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
from __future__ import absolute_import
2+
23
import json
3-
import os
44
import unittest
5+
6+
import os
57
from functools import partial
68

9+
import pytest
10+
from six import StringIO
11+
712
import cwltool.pack
8-
from cwltool.main import print_pack as print_pack
913
import cwltool.workflow
1014
from cwltool.load_tool import fetch_document, validate_document
11-
from cwltool.main import makeRelative
15+
from cwltool.main import makeRelative, main, print_pack
1216
from cwltool.pathmapper import adjustDirObjs, adjustFileObjs
13-
17+
from cwltool.utils import onWindows
1418
from .util import get_data
1519

1620

1721
class TestPack(unittest.TestCase):
22+
maxDiff = None
23+
1824
def test_pack(self):
19-
self.maxDiff = None
2025

2126
document_loader, workflowobj, uri = fetch_document(
2227
get_data("tests/wf/revsort.cwl"))
@@ -38,8 +43,6 @@ def test_pack(self):
3843
def test_pack_missing_cwlVersion(self):
3944
"""Test to ensure the generated pack output is not missing
4045
the `cwlVersion` in case of single tool workflow and single step workflow"""
41-
# Since diff is longer than 3174 characters
42-
self.maxDiff = None
4346

4447
# Testing single tool workflow
4548
document_loader, workflowobj, uri = fetch_document(
@@ -60,3 +63,56 @@ def test_pack_missing_cwlVersion(self):
6063
packed = json.loads(print_pack(document_loader, processobj, uri, metadata))
6164

6265
self.assertEqual('v1.0', packed["cwlVersion"])
66+
67+
def test_pack_idempotence_tool(self):
68+
"""Test to ensure that pack produces exactly the same document for
69+
an already packed document"""
70+
71+
# Testing single tool
72+
self._pack_idempotently("tests/wf/hello_single_tool.cwl")
73+
74+
def test_pack_idempotence_workflow(self):
75+
"""Test to ensure that pack produces exactly the same document for
76+
an already packed document"""
77+
78+
# Testing workflow
79+
self._pack_idempotently("tests/wf/count-lines1-wf.cwl")
80+
81+
def _pack_idempotently(self, document):
82+
document_loader, workflowobj, uri = fetch_document(
83+
get_data(document))
84+
document_loader, avsc_names, processobj, metadata, uri = validate_document(
85+
document_loader, workflowobj, uri)
86+
# generate pack output dict
87+
packed = json.loads(print_pack(document_loader, processobj, uri, metadata))
88+
89+
document_loader, workflowobj, uri2 = fetch_document(packed)
90+
document_loader, avsc_names, processobj, metadata, uri2 = validate_document(
91+
document_loader, workflowobj, uri)
92+
double_packed = json.loads(print_pack(document_loader, processobj, uri2, metadata))
93+
self.assertEqual(packed, double_packed)
94+
95+
@pytest.mark.skipif(onWindows(),
96+
reason="Instance of cwltool is used, on Windows it invokes a default docker container"
97+
"which is not supported on AppVeyor")
98+
def test_packed_workflow_execution(self):
99+
test_wf = "tests/wf/count-lines1-wf.cwl"
100+
test_wf_job = "tests/wf/wc-job.json"
101+
document_loader, workflowobj, uri = fetch_document(
102+
get_data(test_wf))
103+
document_loader, avsc_names, processobj, metadata, uri = validate_document(
104+
document_loader, workflowobj, uri)
105+
packed = json.loads(print_pack(document_loader, processobj, uri, metadata))
106+
temp_packed_path = "/tmp/packedwf"
107+
with open(temp_packed_path, 'w') as f:
108+
json.dump(packed, f)
109+
normal_output = StringIO()
110+
packed_output = StringIO()
111+
self.assertEquals(main(['--debug', get_data(temp_packed_path),
112+
get_data(test_wf_job)],
113+
stdout=packed_output), 0)
114+
self.assertEquals(main([get_data(test_wf),
115+
get_data(test_wf_job)],
116+
stdout=normal_output), 0)
117+
self.assertEquals(json.loads(packed_output.getvalue()), json.loads(normal_output.getvalue()))
118+

tests/wf/count-lines1-wf.cwl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env cwl-runner
2+
class: Workflow
3+
cwlVersion: v1.0
4+
5+
inputs:
6+
file1:
7+
type: File
8+
9+
outputs:
10+
count_output:
11+
type: int
12+
outputSource: step2/output
13+
14+
steps:
15+
step1:
16+
run: wc-tool.cwl
17+
in:
18+
file1: file1
19+
out: [output]
20+
21+
step2:
22+
run: parseInt-tool.cwl
23+
in:
24+
file1: step1/output
25+
out: [output]

tests/wf/parseInt-tool.cwl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env cwl-runner
2+
3+
class: ExpressionTool
4+
requirements:
5+
- class: InlineJavascriptRequirement
6+
cwlVersion: v1.0
7+
8+
inputs:
9+
file1:
10+
type: File
11+
inputBinding: { loadContents: true }
12+
13+
outputs:
14+
output: int
15+
16+
expression: "$({'output': parseInt(inputs.file1.contents)})"

tests/wf/wc-job.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"file1": {
3+
"class": "File",
4+
"location": "whale.txt"
5+
}
6+
}

tests/wf/wc-tool.cwl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env cwl-runner
2+
3+
class: CommandLineTool
4+
cwlVersion: v1.0
5+
6+
inputs:
7+
file1: File
8+
9+
outputs:
10+
output:
11+
type: File
12+
outputBinding: { glob: output }
13+
14+
baseCommand: [wc, -l]
15+
16+
stdin: $(inputs.file1.path)
17+
stdout: output

tests/wf/whale.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Call me Ishmael. Some years ago--never mind how long precisely--having
2+
little or no money in my purse, and nothing particular to interest me on
3+
shore, I thought I would sail about a little and see the watery part of
4+
the world. It is a way I have of driving off the spleen and regulating
5+
the circulation. Whenever I find myself growing grim about the mouth;
6+
whenever it is a damp, drizzly November in my soul; whenever I find
7+
myself involuntarily pausing before coffin warehouses, and bringing up
8+
the rear of every funeral I meet; and especially whenever my hypos get
9+
such an upper hand of me, that it requires a strong moral principle to
10+
prevent me from deliberately stepping into the street, and methodically
11+
knocking people's hats off--then, I account it high time to get to sea
12+
as soon as I can. This is my substitute for pistol and ball. With a
13+
philosophical flourish Cato throws himself upon his sword; I quietly
14+
take to the ship. There is nothing surprising in this. If they but knew
15+
it, almost all men in their degree, some time or other, cherish very
16+
nearly the same feelings towards the ocean with me.

0 commit comments

Comments
 (0)