Skip to content

Commit efc29f3

Browse files
committed
Merge pull request #94 from common-workflow-language/stderr
add stderr support
2 parents 68bfef3 + 8e5f661 commit efc29f3

18 files changed

+434
-30
lines changed

cwltool/cwltest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def run_test(args, i, t): # type: (argparse.Namespace, Any, Dict[str,str]) -> i
114114
if "output" in t:
115115
checkkeys = ["output"]
116116
else:
117-
checkkeys = ["args", "stdin", "stdout", "createfiles"]
117+
checkkeys = ["args", "stdin", "stderr", "stdout", "createfiles"]
118118

119119
for key in checkkeys:
120120
try:

cwltool/draft2tool.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ def rm_pending_output_callback(output_callback, jobcachepending,
206206
j.builder = builder
207207
j.joborder = builder.job
208208
j.stdin = None
209+
j.stderr = None
209210
j.stdout = None
210211
j.successCodes = self.tool.get("successCodes")
211212
j.temporaryFailCodes = self.tool.get("temporaryFailCodes")
@@ -227,6 +228,11 @@ def rm_pending_output_callback(output_callback, jobcachepending,
227228
j.stdin = builder.do_eval(self.tool["stdin"])
228229
reffiles.add(j.stdin)
229230

231+
if self.tool.get("stderr"):
232+
j.stderr = builder.do_eval(self.tool["stderr"])
233+
if os.path.isabs(j.stderr) or ".." in j.stderr:
234+
raise validate.ValidationException("stderr must be a relative path")
235+
230236
if self.tool.get("stdout"):
231237
j.stdout = builder.do_eval(self.tool["stdout"])
232238
if os.path.isabs(j.stdout) or ".." in j.stdout:

cwltool/job.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def __init__(self): # type: () -> None
4444
self.builder = None # type: Builder
4545
self.joborder = None # type: Dict[str,str]
4646
self.stdin = None # type: str
47+
self.stderr = None # type: str
4748
self.stdout = None # type: str
4849
self.successCodes = None # type: Iterable[int]
4950
self.temporaryFailCodes = None # type: Iterable[int]
@@ -137,6 +138,7 @@ def run(self, dry_run=False, pull_image=True, rm_container=True,
137138
env[key] = value
138139

139140
stdin = None # type: Union[IO[Any],int]
141+
stderr = None # type: IO[Any]
140142
stdout = None # type: IO[Any]
141143

142144
scr, _ = get_feature(self, "ShellCommandRequirement")
@@ -146,12 +148,13 @@ def run(self, dry_run=False, pull_image=True, rm_container=True,
146148
else:
147149
shouldquote = needs_shell_quoting_re.search
148150

149-
_logger.info(u"[job %s] %s$ %s%s%s",
151+
_logger.info(u"[job %s] %s$ %s%s%s%s",
150152
self.name,
151153
self.outdir,
152154
" \\\n ".join([shellescape.quote(str(arg)) if shouldquote(str(arg)) else str(arg) for arg in (runtime + self.command_line)]),
153155
u' < %s' % (self.stdin) if self.stdin else '',
154-
u' > %s' % os.path.join(self.outdir, self.stdout) if self.stdout else '')
156+
u' > %s' % os.path.join(self.outdir, self.stdout) if self.stdout else '',
157+
u' \2> %s' % os.path.join(self.outdir, self.stderr) if self.stderr else '')
155158

156159
if dry_run:
157160
return (self.outdir, {})
@@ -178,6 +181,15 @@ def run(self, dry_run=False, pull_image=True, rm_container=True,
178181
else:
179182
stdin = subprocess.PIPE
180183

184+
if self.stderr:
185+
abserr = os.path.join(self.outdir, self.stderr)
186+
dnerr = os.path.dirname(abserr)
187+
if dnerr and not os.path.exists(dnerr):
188+
os.makedirs(dnerr)
189+
stderr = open(abserr, "wb")
190+
else:
191+
stderr = sys.stderr
192+
181193
if self.stdout:
182194
absout = os.path.join(self.outdir, self.stdout)
183195
dn = os.path.dirname(absout)
@@ -191,6 +203,7 @@ def run(self, dry_run=False, pull_image=True, rm_container=True,
191203
shell=False,
192204
close_fds=True,
193205
stdin=stdin,
206+
stderr=stderr,
194207
stdout=stdout,
195208
env=env,
196209
cwd=self.outdir)
@@ -203,6 +216,9 @@ def run(self, dry_run=False, pull_image=True, rm_container=True,
203216
if isinstance(stdin, file):
204217
stdin.close()
205218

219+
if stderr is not sys.stderr:
220+
stderr.close()
221+
206222
if stdout is not sys.stderr:
207223
stdout.close()
208224

cwltool/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ def output_callback(out, processStatus):
183183
a = {"args": job.command_line}
184184
if job.stdin:
185185
a["stdin"] = job.pathmapper.mapper(job.stdin)[1]
186+
if job.stderr:
187+
a["stderr"] = job.stderr
186188
if job.stdout:
187189
a["stdout"] = job.stdout
188190
if job.generatefiles:

cwltool/schemas/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ CWL builds on technologies such as [JSON-LD](http://json-ld.org) and
1414
CWL is designed to express workflows for data-intensive science, such as
1515
Bioinformatics, Medical Imaging, Chemistry, Physics, and Astronomy.
1616

17+
## CWL User Guide
18+
19+
[User guide for the current stable specification (draft-3)](http://www.commonwl.org/draft-3/UserGuide.html),
20+
provides a gentle introduction to writing CWL command line tool and workflow descriptions.
21+
1722
## CWL Specification
1823

1924
The current stable specification is [draft 3](http://www.commonwl.org/draft-3/):
@@ -55,6 +60,10 @@ Some of the software supporting running Common Workflow Language tools or workfl
5560
* [Apache Taverna](http://taverna.incubator.apache.org/),
5661
[Apache Taverna wiki page](https://github.com/common-workflow-language/common-workflow-language/wiki/Taverna)
5762

63+
We continuously run the CWL conformance tests on several implementations:
64+
65+
https://ci.commonwl.org
66+
5867
## Examples
5968

6069
[Github repository of example tools and workflows.](https://github.com/common-workflow-language/workflows)

cwltool/schemas/draft-3/UserGuide.yml

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
- |
2121
```
2222
23-
Use a JSON object in a separate file to describe the input of a run:
23+
Use a YAML object in a separate file to describe the input of a run:
2424
2525
*echo-job.yml*
2626
```
@@ -106,10 +106,11 @@
106106
Notice that "example_file", as a `File` type, must be provided as an
107107
object with the fields `class: File` and `path`.
108108
109-
Invoke `cwl-runner` with the tool wrapper and the input object on the
109+
Next, create a whale.txt and invoke `cwl-runner` with the tool wrapper and the input object on the
110110
command line:
111111
112112
```
113+
$ touch whale.txt
113114
$ cwl-runner inp.cwl inp-job.yml
114115
[job 140020149614160] /home/example$ echo -f -i42 --example-string hello --file=/home/example/whale.txt
115116
-f -i42 --example-string hello --file=/home/example/whale.txt
@@ -218,9 +219,10 @@
218219
- |
219220
```
220221
221-
Invoke `cwl-runner` with the tool wrapper and the input object on the
222+
Next, create a tar file for the example and invoke `cwl-runner` with the tool wrapper and the input object on the
222223
command line:
223224
```
225+
$ touch hello.txt && tar -cvf hello.tar hello.txt
224226
$ cwl-runner tar.cwl tar-job.yml
225227
[job 139868145165200] $ tar xf /home/example/hello.tar
226228
Final process status is success
@@ -308,9 +310,10 @@
308310
- |
309311
```
310312
311-
Invoke `cwl-runner` with the tool wrapper and the input object on the
313+
Create your input files and invoke `cwl-runner` with the tool wrapper and the input object on the
312314
command line:
313315
```
316+
$ rm hello.tar || true && touch goodbye.txt && tar -cvf hello.tar
314317
$ cwl-runner tar-param.cwl tar-param-job.yml
315318
[job 139868145165200] $ tar xf /home/example/hello.tar goodbye.txt
316319
Final process status is success
@@ -374,11 +377,12 @@
374377
- |
375378
```
376379
377-
Now invoke `cwl-runner` providing the tool wrapper and the input object
380+
Provide a hello.js and invoke `cwl-runner` providing the tool wrapper and the input object
378381
on the command line:
379382
380383
```
381-
$ cwl-runner example-docker.cwl example-docker-job.yml
384+
$ echo "console.log(\"Hello World\");" > hello.js
385+
$ cwl-runner docker.cwl docker-job.yml
382386
[job 140259721854416] /home/example$ docker run -i --volume=/home/example/hello.js:/var/lib/cwl/job369354770_examples/hello.js:ro --volume=/home/example:/var/spool/cwl:rw --volume=/tmp/tmpDLs5hm:/tmp:rw --workdir=/var/spool/cwl --read-only=true --net=none --user=1001 --rm --env=TMPDIR=/tmp node:slim node /var/lib/cwl/job369354770_examples/hello.js
383387
Hello world!
384388
Final process status is success
@@ -417,11 +421,12 @@
417421
- |
418422
```
419423
420-
Now invoke `cwl-runner` providing the tool wrapper and the input object
424+
Now create a sample Java file and invoke `cwl-runner` providing the tool wrapper and the input object
421425
on the command line:
422426
423427
```
424-
$ cwl-runner example-arguments.cwl example-arguments-job.yml
428+
$ echo "public class Hello {}" > Hello.java
429+
$ cwl-runner arguments.cwl arguments-job.yml
425430
[job 140051188854928] /home/example$ docker run -i --volume=/home/example/Hello.java:/var/lib/cwl/job710906416_example/Hello.java:ro --volume=/home/example:/var/spool/cwl:rw --volume=/tmp/tmpdlQDWi:/tmp:rw --workdir=/var/spool/cwl --read-only=true --net=none --user=1001 --rm --env=TMPDIR=/tmp java:7 javac -d /var/spool/cwl /var/lib/cwl/job710906416_examples/Hello.java
426431
Final process status is success
427432
{
@@ -855,4 +860,4 @@
855860
The second step `compile` depends on the results from the first step by
856861
connecting the input parameter `src` to the output parameter of `untar`
857862
using `#untar/example_out`. The output of this step `classfile` is
858-
connected to the `outputs` section for the Workflow, described above.
863+
connected to the `outputs` section for the Workflow, described above.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env cwl-runner
2+
class: CommandLineTool
3+
cwlVersion: "cwl:draft-3"
4+
5+
description: |
6+
Generic interface to run a Common Workflow Language tool or workflow from the
7+
command line. To be implemented by each CWL compliant execution platform for
8+
testing conformance to the standard and optionally for use by users.
9+
10+
inputs:
11+
- id: outdir
12+
type: string
13+
default: outdir
14+
description: |
15+
Output directory, defaults to the current directory
16+
inputBinding:
17+
prefix: "--outdir"
18+
19+
- id: quiet
20+
type: boolean
21+
description: no diagnostic output
22+
inputBinding:
23+
prefix: "--quiet"
24+
25+
- id: toolfile
26+
type: [ "null", File ]
27+
description: |
28+
The tool or workflow description to run. Optional if the jobfile has a
29+
`cwl:tool` field to indicate the tool or workflow description to run.
30+
inputBinding:
31+
position: 1
32+
33+
- id: jobfile
34+
type: File
35+
inputBinding:
36+
position: 2
37+
38+
- id: conformance-test
39+
type: boolean
40+
inputBinding:
41+
prefix: "--conformance-test"
42+
43+
- id: basedir
44+
type: string
45+
inputBinding:
46+
prefix: "--basedir"
47+
48+
- id: no-container
49+
type: boolean
50+
description: |
51+
Do not execute jobs in a Docker container, even when listed as a Requirement
52+
inputBinding:
53+
prefix: "--no-container"
54+
55+
- id: tmp-outdir-prefix
56+
type: string
57+
description: |
58+
Path prefix for temporary directories. Useful for OS X so that boot2docker
59+
writes to /Users
60+
inputBinding:
61+
prefix: "--tmp-outdir-prefix"
62+
63+
- id: tmpdir-prefix
64+
type: string
65+
description: |
66+
Path prefix for temporary directories
67+
inputBinding:
68+
prefix: "--tmpdir-prefix"
69+
70+
baseCommand: cwl-runner
71+
72+
stdout: output-object.json
73+
74+
outputs:
75+
- id: output-object
76+
type: File
77+
outputBinding:
78+
glob: output-object.json

cwltool/schemas/draft-4/CommandLineTool.yml

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@ $graph:
3838
- |
3939
## Introduction to draft 4
4040
41-
This specification represents the third milestone of the CWL group.
42-
Since draft-3, this draft introduces the following major changes and additions:
41+
This specification represents the fourth milestone of the CWL group.
42+
Since draft-3, this draft introduces the following changes and additions:
43+
44+
* Preliminary support for initialWorkDir & the Directory type
45+
* "id: name" is now just "name"; "class: Classname" is now just
46+
"Classtname".
4347
4448
## Purpose
4549
@@ -430,6 +434,18 @@ $graph:
430434
doc: |
431435
A path to a file whose contents must be piped into the command's
432436
standard input stream.
437+
- name: stderr
438+
type: ["null", string, "#Expression"]
439+
doc: |
440+
Capture the command's standard error stream to a file written to
441+
the designated output directory.
442+
443+
If `stderr` is a string, it specifies the file name to use.
444+
445+
If `stderr` is an expression, the expression is evaluated and must
446+
return a string with the file name to use to capture stderr. If the
447+
return value is not a string, or the resulting path contains illegal
448+
characters (such as the path separator `/`) it is an error.
433449
- name: stdout
434450
type: ["null", string, "#Expression"]
435451
doc: |

cwltool/schemas/draft-4/UserGuide.yml

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
- |
1818
```
1919
20-
Use a JSON object in a separate file to describe the input of a run:
20+
Use a YAML object in a separate file to describe the input of a run:
2121
2222
*echo-job.yml*
2323
```
@@ -103,10 +103,11 @@
103103
Notice that "example_file", as a `File` type, must be provided as an
104104
object with the fields `class: File` and `path`.
105105
106-
Invoke `cwl-runner` with the tool wrapper and the input object on the
106+
Next, create a whale.txt and invoke `cwl-runner` with the tool wrapper and the input object on the
107107
command line:
108108
109109
```
110+
$ touch whale.txt
110111
$ cwl-runner inp.cwl inp-job.yml
111112
[job 140020149614160] /home/example$ echo -f -i42 --example-string hello --file=/home/example/whale.txt
112113
-f -i42 --example-string hello --file=/home/example/whale.txt
@@ -215,9 +216,10 @@
215216
- |
216217
```
217218
218-
Invoke `cwl-runner` with the tool wrapper and the input object on the
219+
Next, create a tar file for the example and invoke `cwl-runner` with the tool wrapper and the input object on the
219220
command line:
220221
```
222+
$ touch hello.txt && tar -cvf hello.tar hello.txt
221223
$ cwl-runner tar.cwl tar-job.yml
222224
[job 139868145165200] $ tar xf /home/example/hello.tar
223225
Final process status is success
@@ -305,9 +307,10 @@
305307
- |
306308
```
307309
308-
Invoke `cwl-runner` with the tool wrapper and the input object on the
310+
Create your input files and invoke `cwl-runner` with the tool wrapper and the input object on the
309311
command line:
310312
```
313+
$ rm hello.tar || true && touch goodbye.txt && tar -cvf hello.tar
311314
$ cwl-runner example-tar-param.cwl example-tar-param-job.yml
312315
[job 139868145165200] $ tar xf /home/example/hello.tar goodbye.txt
313316
Final process status is success
@@ -371,11 +374,12 @@
371374
- |
372375
```
373376
374-
Now invoke `cwl-runner` providing the tool wrapper and the input object
377+
Provide a hello.js and invoke `cwl-runner` providing the tool wrapper and the input object
375378
on the command line:
376379
377380
```
378-
$ cwl-runner example-docker.cwl example-docker-job.yml
381+
$ echo "console.log(\"Hello World\");" > hello.js
382+
$ cwl-runner docker.cwl docker-job.yml
379383
[job 140259721854416] /home/example$ docker run -i --volume=/home/example/hello.js:/var/lib/cwl/job369354770_examples/hello.js:ro --volume=/home/example:/var/spool/cwl:rw --volume=/tmp/tmpDLs5hm:/tmp:rw --workdir=/var/spool/cwl --read-only=true --net=none --user=1001 --rm --env=TMPDIR=/tmp node:slim node /var/lib/cwl/job369354770_examples/hello.js
380384
Hello world!
381385
Final process status is success
@@ -414,11 +418,12 @@
414418
- |
415419
```
416420
417-
Now invoke `cwl-runner` providing the tool wrapper and the input object
421+
Now create a sample Java file and invoke `cwl-runner` providing the tool wrapper and the input object
418422
on the command line:
419423
420424
```
421-
$ cwl-runner example-arguments.cwl example-arguments-job.yml
425+
$ echo "public class Hello {}" > Hello.java
426+
$ cwl-runner arguments.cwl arguments-job.yml
422427
[job 140051188854928] /home/example$ docker run -i --volume=/home/example/Hello.java:/var/lib/cwl/job710906416_example/Hello.java:ro --volume=/home/example:/var/spool/cwl:rw --volume=/tmp/tmpdlQDWi:/tmp:rw --workdir=/var/spool/cwl --read-only=true --net=none --user=1001 --rm --env=TMPDIR=/tmp java:7 javac -d /var/spool/cwl /var/lib/cwl/job710906416_examples/Hello.java
423428
Final process status is success
424429
{

0 commit comments

Comments
 (0)