全体の構成
-
前振り編
- #1 でテストされる入力ファイルおよび期待される出力の説明
-
実行コマンド生成編
- #1 で実行されるコマンドを CWL ファイルから生成する方法の解説
- Docker & 出力パラメータ編 (いまここ)
- 実行コマンド生成編では取り扱わなかった、Docker 上でコマンドを動かす場合の解説および、出力パラメータの補足方法の解説
また、ダイジェスト版の説明スライド(英語)もあります。
注意!
この記事は CWL に対応したワークフローエンジンを実装する人向けの記事です!
CWL を書いて利用する一般の方は「中の人は大変そうだなぁ」と思いつつ、お茶でも飲みながら軽い気持ちで読んでいただければ幸いです。
実装する人は心を強く持ちましょう。
つづき
昨日の記事では conformance test #1 で記述されたツールの実行コマンドの生成について解説しました。
本日は、昨日説明していなかった DockerRequirement
と出力オブジェクトの捕捉ルールについて解説します。
昨日のおさらい
これと
#!/usr/bin/env cwl-runner
cwlVersion: v1.0
class: CommandLineTool
hints:
- class: ResourceRequirement
coresMin: 2
- class: DockerRequirement
dockerPull: python:2-slim
inputs:
- id: reference
type: File
inputBinding: { position: 2 }
- id: reads
type:
type: array
items: File
inputBinding: { position: 3 }
- id: minimum_seed_length
type: int
inputBinding: { position: 1, prefix: -m }
- id: min_std_max_min
type: { type: array, items: int }
inputBinding:
position: 1
prefix: -I
itemSeparator: ","
- id: args.py
type: File
default:
class: File
location: args.py
inputBinding:
position: -1
outputs:
- id: sam
type: ["null", File]
outputBinding: { glob: output.sam }
- id: args
type:
type: array
items: string
baseCommand: python
arguments:
- bwa
- mem
- valueFrom: $(runtime.cores)
position: 1
prefix: -t
stdout: output.sam
これが
{
"reference": {
"class": "File",
"location": "chr20.fa",
"size": 123,
"checksum": "sha1$hash"
},
"reads": [
{
"class": "File",
"location": "example_human_Illumina.pe_1.fastq"
},
{
"class": "File",
"location": "example_human_Illumina.pe_2.fastq"
}
],
"min_std_max_min": [
1,
2,
3,
4
],
"minimum_seed_length": 3
}
こうなった。
$ python args.py bwa mem -t 2 -I 1,2,3,4 -m 3 chr20.fa example_human_Illumina.pe_1.fastq example_human_Illumina.pe_2.fastq > $PWD/output.sam
$ git status
...
Untracked files:
...
cwl.output.json
...
$ cat cwl.output.json
{"args": ["bwa", "mem", "-t", "2", "-I", "1,2,3,4", "-m", "3", "chr20.fa", "example_human_Illumina.pe_1.fastq", "example_human_Illumina.pe_2.fastq"]}
その結果、以下が余った。
hints:
...
- class: DockerRequirement
dockerPull: python:2-slim
...
outputs:
- id: sam
type: ["null", File]
outputBinding: { glob: output.sam }
- id: args
type:
type: array
items: string
...
DockerRequirement
3.3 節に記載があるように、hints
にあるものは無視しても仕様上は問題無いため、実行コマンドは前回の最終版でも問題ありません。今回は DockerRequirement
をちゃんと処理する場合を考えます。
hints:
...
- class: DockerRequirement
dockerPull: python:2-slim
仕様の5.5 節に以下の記述があります。
The platform must execute the tool in the container using
docker run
with the appropriate Docker image and tool command line.
The workflow platform may provide input files and the designated output directory through the use of volume bind mounts.
Docker 上でコマンドを実行するために、以下の処理が必要です。
- 各引数の
File
オブジェクトを volume mount します。-
File
オブジェクトから生成された引数は、args.py
、chr20.fa
、example_human_Illumina.pe_1.fastq
、example_human_Illumina.pe_2.fastq
の4種類です。それぞれ-v
で、docker コンテナ内からアクセスできるようにします。
-
$ docker run -it --rm -v $PWD/v1.0/args.py:/staged/args.py:ro -v $PWD/v1.0/chr20.fa:/staged/chr20.fa:ro -v $PWD/v1.0/example_human_Illumina.pe_1.fastq:/staged/example_human_Illumina.pe_1.fastq:ro -v $PWD/v1.0/example_human_Illumina.pe_2.fastq:/staged/example_human_Illumina.pe_2.fastq:ro python:2-slim ...
- 最初に示したように、
args.py
はカレントディレクトリにcwl.output.json
を出力します。docker run
内で出力したファイルは、何もしなければ虚空の彼方に消えてしまうので、出力ディレクトリを volume mount する必要があります。4.2 節を見ると、出力ディレクトリはHOME
として参照できないといけないため、-e
オプションを用いてHOME
を特定のディレクトリ(docker 内の場合は適当なディレクトリで問題ありません)を指すようにします。また、HOME
で指定したディレクトリを volume mount で docker 外から参照できるようにします。最後に、--workdir
でワーキングディレクトリを指定します。今回は-e HOME=/output -v $PWD:/output:rw --workdir=/output
を追加して、これらを行えるようにします。
$ pwd
/Users/tom-tan/common-workflow-language/v1.0
$ docker run -it --rm -e HOME=/output -v $PWD:/output:rw --workdir=/output -v $PWD/v1.0/args.py:/staged/args.py:ro -v $PWD/v1.0/chr20.fa:/staged/chr20.fa:ro -v $PWD/v1.0/example_human_Illumina.pe_1.fastq:/staged/example_human_Illumina.pe_1.fastq:ro -v $PWD/v1.0/example_human_Illumina.pe_2.fastq:/staged/example_human_Illumina.pe_2.fastq:ro python:2-slim python /staged/args.py bwa mem -t 2 -I 1,2,3,4 -m 3 /staged/chr20.fa /staged/example_human_Illumina.pe_1.fastq /staged/example_human_Illumina.pe_2.fastq > $PWD/output.sam
出力オブジェクトの補足
outputs:
- id: sam
type: ["null", File]
outputBinding: { glob: output.sam }
- id: args
type:
type: array
items: string
上を見ると、sam
と args
の二種類の出力オブジェクトがあることがわかります。
sam
の型の ["null", File]
については、Issue 605 を確認することで、「"null"
型か File
型のどちらかを取る型」(Issue 中では union type)であることがわかります。
5.2.3 節を参照すると、outputBinding.glob
で指定したファイル名 output.sam
がある場合には、その output.sam
を表す File
オブジェクトが返ることがわかります1。今回はコマンドの標準出力が output.sam
にリダイレクトされるので、このファイルを表す File
オブジェクトが(その中身の有無にかかわらず) sam
として返る…
と思うけどそうはならないのが conformance test #1!
出力オブジェクトの説明をガン無視する cwl.output.json
に関する仕様
仕様をよく読むと、4.4 節に cwl.output.json
に関する記述があります。曰く:
If the output directory contains a file named "cwl.output.json", that file must be loaded and used as the output object.
つまり、
$ cat cwl.output.json
{"args": ["bwa", "mem", "-t", "2", "-I", "1,2,3,4", "-m", "3", "chr20.fa", "example_human_Illumina.pe_1.fastq", "example_human_Illumina.pe_2.fastq"]}
この内容がこのまま出力オブジェクトになります。glob
をどう解釈するとかこのテストには全く関係ありません。
最終的に出力オブジェクトは、cwl.output.json
の中身である
{
"args": [
"bwa",
"mem",
"-t",
"2",
"-I",
"1,2,3,4",
"-m",
"3",
"chr20.fa",
"example_human_Illumina.pe_1.fastq",
"example_human_Illumina.pe_2.fastq"
]
}
となります。値が null
になっているフィールドは省略できる2ため、sam
フィールドは出力オブジェクトには現れません。
まとめ
大変でしたね!
CWL でツール定義を書く人や、他の人から CWL ファイルを受け取って実行する人は、上記を気にしなくてもツールやワークフローの実行が可能です。全て処理系が善きにはからってくれます。
処理系を作る人は頑張りましょう!
…個人的には、最初は単純な ExpressionTool (#14 や #15) から始めるのがとっつきやすいと思います。