全体の構成
-
前振り編
- #1 でテストされる入力ファイルおよび期待される出力の説明
- 実行コマンド生成編 (いまここ)
- #1 で実行されるコマンドを CWL ファイルから生成する方法の解説
-
Docker & 出力パラメータ編
- 実行コマンド生成編では取り扱わなかった、Docker 上でコマンドを動かす場合の解説および、出力パラメータの補足方法の解説
また、ダイジェスト版の説明スライド(英語)もあります。
注意!
この記事は CWL に対応したワークフローエンジンを実装する人向けの記事です!
CWL を書いて利用する一般の方は「中の人は大変そうだなぁ」と思いつつ、お茶でも飲みながら軽い気持ちで読んでいただければ幸いです。
実装する人は心を強く持ちましょう。
つづき
昨日の記事では「僕の考えた最強のワークフローエンジン」の最初の(そしてかなり大きな)壁である conformance test #1 の入力パラメータや出力例などを簡単に示しました。
次回は、このテストの突破に必要な仕様の解説を行います。
と書きましたが、分量が増えたので、今回は DockerRequirement
と出力パラメータを適用する前の段階まで解説を行います。
早速解説
4.1 節に CWL での実行コマンド生成について記述があります。
以下が概要です。各番号は 4.1 節での手順に対応しています。
-
arguments
からCommandLineBinding
オブジェクトを収集する。 -
inputs
からCommandLineBinding
オブジェクトを収集し、入力オブジェクトをそれぞれ関連付ける。 -
inputs
から収集したCommandLineBinding
にソート用のキーを対応付ける - いい感じにソートする。
- 各
CommandLineBinding
オブジェクトに対して、引数生成ルールを適用する。 - 先頭に
baseCommand
を挿入して完成!
1. arguments
から CommandLineBinding
オブジェクトを収集する (いきなり仕様にない: #806)
4.1 節 の 1 は以下になります。
Collect
CommandLineBinding
objects fromarguments
. Assign a sorting key[position, i]
whereposition
isCommandLineBinding.position
andi
is the index in thearguments
list.
CommandLineBinding
は、5.1.2 節 で定義されたフィールドを持つオブジェクトのことです。今回の例では、最後の要素がそれに当たります。
じゃあ最初の2つはなんなんだ…?と思って仕様を確認すると、arguments
の要素として文字列も来る場合があると記述がありますが、arguments
に CommandLineBinding
以外が来る場合の処理についてはどこにも記述がありません。
arguments:
- bwa
- mem
- valueFrom: $(runtime.cores)
position: 1
prefix: -t
CWL project leader の Michael R. Crusoe 氏に確認すると、以下のような CommandLineBinding
と置き換えても実装上は問題ないとのことでした。
arguments:
- valueFrom: bwa
- valueFrom: mem
- valueFrom: $(runtime.cores)
position: 1
prefix: -t
これについては Issue #806 としてすでに登録されているので、v1.1 では明文化されるはずです。以下では "bwa"
と "mem"
を、上記の同値な CommandLineBinding
オブジェクトとして取り扱います。
次に、各要素とソート用のキーを割り当てます。
5.1.2 節の position
の項を見ると、
The sorting key. Default position is 0.
とあることから、以下のようにキーと CommandLineBinding
オブジェクトを関連付けられます。
キー([position, i] ) |
CommandLineBinding オブジェクト |
---|---|
[0, 0] |
"bwa" |
[0, 1] |
"mem" |
[1, 2] |
{ "valueFrom": ...} |
2. inputs
から CommandLineBinding
オブジェクトを収集し、入力オブジェクトをそれぞれ関連付ける。
以下が 4.1 節 の 2 です。
Collect
CommandLineBinding
objects from theinputs
schema and associate them with values from the input object. Where the input type is a record, array, or map, recursively walk the schema and input object, collecting nestedCommandLineBinding
objects and associating them with values from the input object.
inputBinding
以下が、inputs
の CommandLineBinding
オブジェクトです。
また bwa-mem-job.json
が入力オブジェクトです。
まずは入力オブジェクトを見ていきましょう。
default
パラメータ
入力オブジェクトを見ると、フィールドが4つあることがわかります。
{
"reference": ...,
"reads": ...,
"min_std_max_min": ...,
"minimum_seed_length": ...
}
それぞれ CWL ファイルの inputs
の各フィールドが対応しているはずなのですが…
inputs:
- id: reference
...
- id: reads
...
- id: minimum_seed_length
...
- id: min_std_max_min
...
- id: args.py # <------------ !?
...
あぁん…?
何が起きてるの
入力パラメータ args.py
を詳しく見ると、以下のようになっています。
inputs:
...
- id: args.py
type: File
default:
class: File
location: args.py
...
CommandLineTool の 5.0 節には以下の記述があります。
If an input parameter is missing from the input object, it must be assigned a value of
null
(or the value ofdefault
for that parameter, if provided) ...
そのため default
フィールドで指定された args.py
ファイル (厳密には v1.0/bwa-mem-tool.cwl
と同一ディレクトリにある args.py
) が代わりに args.py
パラメータの値として使用されます。パラメータ名とファイル名が同じなのでややこしい…
無視される入力パラメータのフィールド (仕様にない!: #808, #811)
さて、今度は reference
パラメータを見てみましょう。
{
"reference": {
"class": "File",
"location": "chr20.fa",
"size": 123,
"checksum": "sha1$hash"
},
...
}
「"size": 123
とあるから中身があるファイルなのね」と思い、実際に中身を確認すると…
$ wc -c v1.0/chr20.fa
0
中身は空です。それによく見たら checksum
も有効な sha1 ハッシュ値ではありません。
これをどう扱うべきか仕様を確認すると…
どこにも記述がない
- 本来的には: 仕様にどうなるべきか(あるいは動作が実装依存だと)記述があるべき
- 現状: 仕様に一切記述がないが、このテストはパスする意図で書かれている。おそらく入力パラメータの
File
オブジェクトのsize
やchecksum
等のパラメータは無視していいのだろう…
今回は、入力オブジェクトの size
や checksum
が間違っていても受け付けてくれるゆるい処理系を前提にして話を進めます1。
3. inputs
から収集した CommandLineBinding
にソート用のキーを対応付ける
以下が4.1 節 の 3 項です。
Create a sorting key by taking the value of the
position
field at each level leading to each leaf binding object. Ifposition
is not specified, it is not added to the sorting key. For bindings on arrays and maps, the sorting key must include the array index or map key following the position. If and only if two bindings have the same sort key, the tie must be broken using the ordering of the field or parameter name immediately containing the leaf binding.
つまり、
- 基本は
position
(省略時は 0) -
position
が同じ場合にはフィールド名
この条件に従ってキーを対応付けたものが以下になります。
キー | フィールド名 |
---|---|
[2, "reference"] |
reference |
[3, "reads"] |
reads |
[1, "minimum_seed_length"] |
minimum_seed_length |
[1, "min_std_max_min"] |
min_std_max_min |
[-1, "args.py"] |
args.py |
4. いい感じにソートする。
arguments
と inputs
での CommandLineBinding
オブジェクトをソート用キーと一緒にまとめたものが以下の表です。
キー |
CommandLineBinding オブジェクト |
---|---|
[0, 0] |
"bwa" |
[0, 1] |
"mem" |
[1, 2] |
{ "valueFrom": ...} |
[2, "reference"] |
reference の inputBinding
|
[3, "reads"] |
reads の inputBinding
|
[1, "minimum_seed_length"] |
minimum_seed_length の inputBinding
|
[1, "min_std_max_min"] |
min_std_max_min の inputBinding
|
[-1, "args.py"] |
args.py の inputBinding
|
4.1 節 の 4 の
Sort elements using the assigned sorting keys. Numeric entries sort before strings.
に従いソートすることで、以下の表が得られます。
キー |
CommandLineBinding オブジェクト |
---|---|
[-1, "args.py"] |
args.py の inputBinding
|
[0, 0] |
"bwa" |
[0, 1] |
"mem" |
[1, 2] |
{ "valueFrom": ...} |
[1, "min_std_max_min"] |
min_std_max_min の inputBinding
|
[1, "minimum_seed_length"] |
minimum_seed_length の inputBinding
|
[2, "reference"] |
reference の inputBinding
|
[3, "reads"] |
reads の inputBinding
|
5. 各 CommandLineBinding
オブジェクトに対して、引数生成ルールを適用する。
あとは4.1 節 の 5 にあるように、
In the sorted order, apply the rules defined in
CommandLineBinding
to convert bindings to actual command line elements.
を実行します。
5 中の rules
は、5.1.2 節を参照してください。
以下では各 CommandLineBinding
オブジェクトを、コマンドライン引数に変換していきます。
inputs
の args.py
inputs:
...
- id: args.py
type: File
default:
class: File
location: args.py
inputBinding:
position: -1
...
仕様には以下のようにあるため、単に path
フィールドの値を使えばいいようです。
File
: Addprefix
and the value ofFile.path
to the command line.
しかし与えられたパラメータには location
フィールドしかありません。
ここでFile
オブジェクトの仕様 (5.1.5 節)を確認すると、path
については次のように書かれています。
Alternately to
location
, implementations must also accept thepath
property on File, which must be a filesystem path available on the same host ...
今回は location
もローカルのファイルパスを指しているため、単に File.location
の値(つまり "args.py"
)に変換できます。
arguments
内の "bwa"
、"mem"
仕様には、
`string: Add prefix and the string to the command line.
とあるため、それぞれ "bwa"
と "mem"
に変換できます。
arguments
内のややこしそうな CommandLineBinding
オブジェクト
arguments:
...
- valueFrom: $(runtime.cores)
position: 1
prefix: -t
5.1.2 節 を見ると、
For binding objects listed in
CommandLineTool.arguments
, the term "value" refers to the effective value after evaluatingvalueFrom
.
とあるので、まず valueFrom
を評価する必要があります。
-
$(...)
については、3.4 節 および3.5 節に記載されています。これは Expression と呼ばれるもので、実行時のパラメータ (入力オブジェクトや出力ディレクトリ)などに評価される場合(3.4 節)と、JavaScript のコードとして評価される場合(3.5 節)があります。今回はInlineJavascriptRequirement
は使われていないので、3.4 節のみを参照すればいいです。 -
runtime
が取りうるフィールドについては 4.2 節を参照すればよいです。以下を見ると、$(runtime.cores)
を CPU コア数に評価すれば良いことがわかります。- 本文を見ると、
See ResourceRequirement for details on how to describe the hardware resources required by a tool.
とも書いてあるため、今回はResourceRequirement
の項も見る必要があります。
- 本文を見ると、
runtime.cores
: number of CPU cores reserved for the tool process
-
5.12 節に
ResourceRequirement
についての記述があります。今回はcoresMin: 2
と指定があり、また 5.12 節にIf "min" is specified but "max" is not, then "max" == "min"
と記述があるため、最終的に$(runtime.cores)
が2
に評価されることがあります。
hints:
- class: ResourceRequirement
coresMin: 2
- 注目すべきは
hints
です。3.3 節 を見ると、A hint is similar to a requirement; however, it is not an error if an implementation cannot satisfy all hints.
とあります。つまり実行エンジンは、このResourceRequirement
を無視して、$(runtime.cores)
で 2 以外の値を返しても仕様上は問題ありません。今回は$(runtime.cores)
が 2 を返す場合を考えます。
5.1.2 節 に数値の変換規則が
number
: Addprefix
and decimal representation to command line.
とあるので、最終的に ["-t", 2]
に変換できます2。
inputs
の min_std_max_min
inputs:
...
- id: min_std_max_min
type: { type: array, items: int }
inputBinding:
position: 1
prefix: -I
itemSeparator: ","
...
{
...
"min_std_max_min": [
1,
2,
3,
4
],
...
}
仕様には以下のようにあります。["-I", "1,2,3,4"]
に変換すればよいです。
number
: Addprefix
and decimal representation to command line.
...
array
: IfitemSeparator
is specified, addprefix
and the join the array into a single string withitemSeparator
separating the items. ...
minimum_seed_length
inputs:
...
- id: minimum_seed_length
type: int
inputBinding: { position: 1, prefix: -m }
...
{
...
"minimum_seed_length": 3
}
仕様には以下のようにあります。["-m", 3]
に変換すればよいです。
number
: Addprefix
and decimal representation to command line.
inputs
の reference
inputs:
- id: reference
type: File
inputBinding: { position: 2 }
{
"reference": {
"class": "File",
"location": "chr20.fa",
...
},
...
}
仕様には以下のように書いてあるので、"chr20.fa"
に変換できます。
File
: Addprefix
and the value ofFile.path
to the command line.
inputs
の reads
inputs:
...
- id: reads
type:
type: array
items: File
inputBinding: { position: 3 }
...
{
...
"reads": [
{
"class": "File",
"location": "example_human_Illumina.pe_1.fastq"
},
{
"class": "File",
"location": "example_human_Illumina.pe_2.fastq"
}
],
...
}
仕様には以下のようにあります。今回は itemSeparator
も prefix
も指定されていないので、単に ["example_human_Illumina.pe_1.fastq", "example_human_Illumina.pe_2.fastq"]
を返せばいいです。
File
: Addprefix
and the value ofFile.path
to the command line.
...
array
: IfitemSeparator
is specified, ... Otherwise first addprefix
, then recursively process individual elements.
最後に変換したものをまとめると、以下のようになります。
- "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"]
6. 先頭に baseCommand
を挿入して完成!
Insert elements from
baseCommand
at the beginning of the command line.
baseCommand: python
先程変換したものに追加して、実行コマンドの完成です!
$ 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
標準出力の捕捉
与えられたツール定義に stdout
や stderr
フィールドがある場合、コマンドの標準出力や標準エラー出力をファイルとしてリダイレクトする必要があります (5 節)。
stdout: output.sam
今回の場合、標準出力の結果が output.sam
にリダイレクトされます。
また、その時の出力ディレクトリは $(runtime.outdir)
で指定されたディレクトリです(cwltool
の場合、デフォルトは $PWD
で、--outdir
でも設定できます)。
例えば、出力ディレクトリが $PWD
の場合、リダイレクトも含めた最終的な実行コマンドは以下になります。
$ 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
動くか確認してみる
$ pwd
/Users/tom-tan/common-workflow-language
$ cd v1.0/1.0
$ 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
$ ls output.sam
output.sam
うまく動いてるようですね!
これで全部?
実はまだすべきことがあります。
例えば、
$ pwd
/Users/tom-tan/common-workflow-language/v1.0/v1.0
$ 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"]}
先程のコマンドは output.sam
以外にもファイルを出力していますが、これは一体どう扱えばいいのでしょうか?
また、実行コマンド生成に使っていないパーツが残っているようです。
...
hints:
...
- class: DockerRequirement
dockerPull: python:2-slim
...
outputs:
- id: sam
type: ["null", File]
outputBinding: { glob: output.sam }
- id: args
type:
type: array
items: string
具体的には、
- コマンドを Docker コンテナで動かすときに注意すべきことは? (実装者視点)
- 出力オブジェクトのキャプチャーどうするの
- 頑張って解説した出力オブジェクトのキャプチャルールを台無しにする
cwl.output.json
つづく
次回で全部解説できるはず!
-
この場合の挙動について仕様には記載がないため、この不届きな入力に対して
permanentFailure
で落ちる場合も、それとなく無視して実行される場合も仕様に従った動作と言える可能性があります。皆さんは(動作が処理系依存になりうる)このような入力ファイルは用意しないようにしましょうね! ↩ -
が、先程も述べたように
$(runtime.cores)
は2以外にも解釈されうるので、["-t", 3]
や["-t", 4]
でも仕様上は間違っていません。しかしrun_tesh.sh
コマンドは$(runtime.cores)
が2
にならないとテストが通らないため、実は conformance test #1 は特定の処理系に依存したテストになってしまっています(報告済み)。皆さんも環境や処理系に依存したテストは書かないように気をつけましょうね! ↩