はじめに
この記事は CWL Advent Calendar の24日目の記事です。
ところで Wikipedia によると、クリスマスイブにはサンタがプレゼントをくれるそうです。
- (命題) 今日はクリスマスイブ ⇒ 私はサンタクロースからプレゼントをもらえる
- (上の対偶) 私はサンタクロースからプレゼントをもらっていない ⇒ 今日はクリスマスイブではない
まだプレゼントを貰っていないので、まだクリスマスイブは始まっていません!
この記事は12月24日に公開されるはずなので、この記事が公開されることでクリスマスイブが始まり、私は何かプレゼントが貰えるはずです。
前回のつづき
前回は、既存のツールが最低限動くように CWL を書く方法を紹介しました。
この記事では、前回のツール定義を拡張して、よりそれらしい CWL を書く方法を紹介します。
前回はこれが
$ head -n5 foobar.txt > foobar-head.txt
こうなった
cwlVersion: v1.0
class: CommandLineTool
baseCommand: [head]
arguments: [-n$(inputs.nlines), $(inputs.source)]
inputs:
- id: source
type: File
- id: nlines
type: int
outputs:
- id: out
type: stdout
stdout: $(inputs.source.nameroot)-head.txt
requirements:
- class: DockerRequirement
dockerPull: debian:latest # alpine:latest だと動かない例があるので注意!
拡張方針いろいろ
1. ヘルプを追加したい
CWL のリファレンス実装の cwltool
は、デフォルトで与えられた CWL ファイルの使い方を示す --help
オプションを提供してくれます。
$ cwltool head.cwl --help
...
usage: head.cwl [-h] --source SOURCE --nlines NLINES [job_order]
positional arguments:
job_order Job input json file
optional arguments:
-h, --help show this help message and exit
--source SOURCE
--nlines NLINES
しかしユーザーがほしいのは head
コマンド自体の --help
の結果かもしれません。--help
の結果を出力するためのパラメータを追加してみましょう。
基本方針ですが、前回と同様に arguments
に追加する方法だとうまくいきません。というのも、arguments
に書いた部分は、常に実行コマンド上に現れてしまうからです。
今回のように、パラメータの値によって実行コマンド上に引数が現れたり現れなかったりする場合には、inputBinding
を活用します。
具体的な追加方法は以下になります。
ヘルプ表示用のパラメータ show_help
を追加する
inputs:
- id: source
...
- id: nlines
...
- id: show_help
...
id
を help
にしてしまうと、cwltool
が組み込みで提供する --help
と衝突してエラーになります。別の名前にしましょう。
show_help
の型を追加する
今回のように表示する・しないのような二値には、boolean
型を指定します。
boolean
型のパラメータは、値が true
の時のみに実行コマンド中に現れます (inputBinding
がある場合1)。
inputs:
...
- id: show_help
type: boolean
...
show_help
が true
の時に表示する引数を追加する
show_help
が true
の時に、実行コマンドに --help
を追加するためには inputBinding#prefix
フィールドを使用します。
inputs:
...
- id: show_help
type: boolean
inputBinding:
prefix: --help
...
default
でデフォルト値を設定する
上記まででヘルプをサポートすることができました。
$ cwltool head.cwl --source manhead.txt --nlines 5 --show_help
...
[job head.cwl] completed success
{
"out": {
"location": "file:///Users/tanjo/manhead-head.txt",
"basename": "manhead-head.txt",
"class": "File",
"checksum": "sha1$30c2a20de147871db76e237705ac274277504428",
"size": 145,
"path": "/Users/tanjo/manhead-head.txt"
}
}
Final process status is success
$ cat manhead-head.txt
Usage: head [OPTION]... [FILE]...
Print the first 10 lines of each FILE to standard output.
With more than one FILE, precede each with a header giving the file name.
...
しかし、今度はヘルプが必要ない場合にも show_help
を false
にわざわざ指定する必要があります2。
default
フィールドを使うことで、show_help
を指定しない場合には false
を暗黙的に指定させることができます。
inputs:
...
- id: show_help
type: boolean
inputBinding:
prefix: --help
default: false
...
2. 複数ファイルを渡したい
1 で作成したヘルプの出力を確認してみます。
$ cwltool head.cwl --source manhead.txt --nlines 5 --show_help
...
$ cat manhead-head.txt
Usage: head [OPTION]... [FILE]...
...
これを見ると、head
コマンドはファイルを複数受け取ることができるのがわかります。
CWL 側でも複数ファイルを受け取れるように拡張しましょう。
source
の型を変更する
source
の型を、File
の配列を受け取れるように変更します。
File
の配列は File[]
型で表現できます。
inputs:
- id: source
type: File[]
...
出力ファイル名をいい感じにする
前回の段階では、出力ファイル名を以下のように指定していました。
stdout: $(inputs.source.nameroot)-head.txt
しかし source
の型を File
から File[]
に変更してしまったため、このままでは nameroot
プロパティが使えません。
対策には以下の2つの方法が考えられます。
- 出力ファイル名を決め打ちにする。
- シンプル!
stdout: output-head.txt
- 最初に渡したファイル名を元に出力ファイル名を決める
- 配列パラメータ
source
のn
番目の要素は、$(inputs.arr[n])
という記法で取得できます。nameroot
プロパティと組み合わせると、0
番目の要素のnameroot
の結果を取得できます。
- 配列パラメータ
stdout: $(inputs.source[0].nameroot)-head.txt
できあがり!
やったぜ。
$ cwltool head.cwl --nlines 5 --source manhead.txt --source head.cwl
...
[job head.cwl] completed success
{
"out": {
"location": "file:///Users/tanjo/manhead-head.txt",
"basename": "manhead-head.txt",
"class": "File",
"checksum": "sha1$c255a9e8f91aeb2ba1dce30caf11d102c89414d6",
"size": 407,
"path": "/Users/tanjo/manhead-head.txt"
}
}
Final process status is success
$ cat manhead-head.txt
==> /var/lib/cwl/stg39c3c00a-51ac-4e1c-924f-6d41d724be0e/manhead.txt <==
HEAD(1) BSD General Commands Manual HEAD(1)
NAME
head -- display first lines of a file
==> /var/lib/cwl/stg902fd9f1-d682-4aec-a18a-234962aefe35/head.cwl <==
cwlVersion: v1.0
class: CommandLineTool
baseCommand: [head]
arguments: [-n$(inputs.nlines), $(inputs.source)]
inputs:
その他
パイプを使ってうまいこと実行してほしい
シェルスクリプトでは、以下のようにパイプで複数のコマンドをつなげることで、中間ファイルを生成させずに複数のコマンドを効率よく実行することができます。
$ cat inputs.txt | head -n5 | sort -nr > output.txt # パイプを使った素敵な処理
しかし、前回作成した CWL を使ったワークフローでは、パイプ等を使った効率的な実行ができません。
これを解決するには、ファイル型の入力に対して streamable: true
を追加します。
...
inputs:
- id: source
type: File
streamable: true
...
こうすることで、CWL 処理系は、source
がストリーム処理が可能3(== パイプが利用可能)であることを検知して、パイプを使って効率的に実行してくれるかもしれません4。
[Advanced] show_help
を指定する時には他のパラメータを省略したい
show_help
を指定した時には source
や nlines
を省略できるようにしたい、という時には直和型と直積型を利用します。
闇への扉(雑に書けない)を開いてしまったので、本項目では省略します。
それでも書く必要がある方は、ユーザーガイドを参照してください。
注意: この機能を使用すると、柔軟な指定が可能になる代わりに可読性が低下します。本当に必要かどうかをよく考えて使いましょう!
[Advanced] 渡したファイルを同時に処理してほしい
ツール定義はあくまでツールを一回実行するためのコマンドライン生成や実行を行うためのものなので、その範囲内では並列処理はできません。
複数ファイルを並列処理させるには、scatter
を用いたワークフロー定義を書く必要があります。
書き方はユーザーガイドを参照してください。
まとめ
ここまでの項目をマスターすれば、おおよそのツールは CWL 化できるようになると思います。
より深く知りたい方は、以下のリンクを読んでみるといいでしょう。