The Workflow Description Language (WDL) is a way to specify data processing workflows with a human-readable and -writeable syntax. WDL makes it straightforward to define analysis tasks, chain them together in workflows, and parallelize their execution. The language makes common patterns simple to express, while also admitting uncommon or complicated behavior; and strives to achieve portability not only across execution platforms, but also different types of users. Whether one is an analyst, a programmer, an operator of a production system, or any other sort of user, WDL should be accessible and understandable.
WDL的官方说明文档: Getting Started With WDL
目前在GATK4的流程中,基本都是以WDL
的形式编写的。WDL
没有太多复杂的逻辑和语法,入门简单。首先看一个hello world
的例子
workflow myWorkflow {
call myTask
}
task myTask {
command {
echo "hello world"
}
output {
String out = read_string(stdout())
}
}
对于一个WDL脚本而言,有以下5个核心结构
1.workflow: 工作流定义
- task : 工作流包含的任务定义
- call: 调用或触发工作流里面的 task 执行
- command: task在计算节点上要执行的命令行
- output: task 或 workflow 的输出定义
- runtime: task在计算节点上的运行时参数,包括 CPU、内存、docker 镜像等
每个脚本包含1个workflow
, workflow
由多个task
构成。 在workflow
中,通过call调用对应的task
。每个task
在workflow
代码块之外单独定义。
task
代表任务,读取输入文件,执行相应命令,然后输出。command
中对应的就是执行的命令,比如一条具体的gatk的命令,output
指定task
的输出值。可以将task
理解为编程语言中的函数,每个函数读取输入的参数,执行代码,然后返回,command
对应执行的具体代码,output
对应返回值。
在WDL中,也是可以传递参数的。task
和workflow
中的写法不同
变量利用
我们要把一个处理步骤构造成一个task
, 就要封装计算软件的命令行,那么命令行的参数如何传入呢?输出文件的名字如何指定呢?这些问题在 WDL 中可以通过变量来解决。比如 Hello world
例子中的 String out 就是一个字符串类型的输入,用于指定输出文件的名字。WDL 中的变量可以定义在 workflow 中,也可以定义在 task中。在command
和 output
中可以通过$
和{}
的方式来引用变量。
变量的类型主要有以下几种:
- String
- Int
- Float
- File
- Boolean
- Array[T]
- Map[K, V]
- Pair[X, Y]
- Object
workflow 中的参数
下面的示意图中, workflow 有3个参数,文件类型的my_ref,my_input 和字符串类型的name。传递这3个参数给task时,直接传变量名就可以了。
task 中的参数
下面的示意图中,task 有3个输入的参数,文件类型的ref,in 和字符串类型的id。 在command中,通过${ref}这种格式访问变量的值。
Task 如何组成Workflow呢
作为流程管理语言,需要对多个task统一管理。task之间具有多种关系
1. 线性输出关系:
第一种是最常见的场景,简单的线性串联,多个 task 依次执行,前面步骤的输出作为后面步骤的输入,最后一个 task 的输出作为整个 workflow 的输出。
示例如下:
workflow LinearChain {
File firstInput
call stepA { input: in=firstInput }
call stepB { input: in=stepA.out }
call stepC { input: in=stepB.out }
}
task stepA {
File in
command { programA I=${in} O=outputA.ext }
output { File out = "outputA.ext" }
}
task stepB {
File in
command { programB I=${in} O=outputB.ext }
output { File out = "outputB.ext" }
}
task stepC {
File in
command { programC I=${in} O=outputC.ext }
output { File out = "outputC.ext" }
}
2. 多对多的依赖关系
一个task的输出作为多个task的输入,或者多个task的输出作为1个task的输入
case1:
workflow MultiOutMultiIn {
File firstInput
call stepA { input: in=firstInput }
call stepB { input: in=stepA.out }
call stepC { input: in1=stepB.out1, in2=stepB.out2 }
}
task stepA {
File in
command { programA I=${in} O=outputA.ext }
output { File out = "outputA.ext" }
}
task stepB {
File in
command { programB I=${in} O1=outputB1.ext O2=outputB2.ext }
output {
File out1 = "outputB1.ext"
File out2 = "outputB2.ext" }
}
task stepC {
File in1
File in2
command { programB I1=${in1} I2=${in2} O=outputC.ext }
output { File out = "outputC.ext" }
}
case2:
示例如下:
workflow BranchAndMerge {
File firstInput
call stepA { input: in=firstInput }
call stepB { input: in=stepA.out }
call stepC { input: in=stepA.out }
call stepD { input: in1=stepC.out, in2=stepB.out }
}
task stepA {
File in
command { programA I=${in} O=outputA.ext }
output { File out = "outputA.ext" }
}
task stepB {
File in
command { programB I=${in} O=outputB.ext }
output { File out = "outputB.ext" }
}
task stepC {
File in
command { programC I=${in} O=outputC.ext }
output { File out = "outputC.ext" }
}
task stepD {
File in1
File in2
command { programD I1=${in1} I2=${in2} O=outputD.ext }
output { File out = "outputD.ext" }
}
3. 平行执行关系(并行计算)
workflow ScatterGather {
Array[File] inputFiles
scatter (oneFile in inputFiles) {
call stepA { input: in=oneFile }
}
call stepB { input: files=stepA.out }
}
task stepA {
File in
command { programA I=${in} O=outputA.ext }
output { File out = "outputA.ext" }
}
task stepB {
Array[File] files
command { programB I=${files} O=outputB.ext }
output { File out = "outputB.ext" }
}
task
和函数还是有一定的区别,函数可以在代码中多次调用,但是task
多次调用会有风险。下面的示意图中,stepA 运行两次,一次作为stepB的输入,一次作为stepC的输入。如果stepA的两次调用并行执行,当执行完之后,在传递给下一个task时,由于存在两个同名的stepA, stepB和stepC 就会无法正确接受参数。
WDL
中提供了解决方案,叫做task alias
, 为task
起一个别名,示例如下
workflow taskAlias {
File firstInput
File secondInput
call stepA as firstSample { input: in=firstInput }
call stepA as secondSample { input: in=secondInput }
call stepB { input: in=firstSample.out }
call stepC { input: in=secondSample.out }
}
task stepA {
File in
command { programA I=${in} O=outputA.ext }
output { File out = "outputA.ext" }
}
task stepB {
File in
command { programB I=${in} O=outputB.ext }
output { File out = "outputB.ext" }
}
task stepC {
File in
command { programC I=${in} O=outputC.ext }
output { File out = "outputC.ext" }
}
在WDL脚本中, 理论上每个task
只可以调用1次,如果希望多次调用,必须借助task alias
。
输入参数如何传入
workflow 的输入,比如基因样本的存储位置、计算软件的命令行参数、计算节点的资源配置等,可以通过 json 文件的形式来指定。使用 wdltools 工具可以根据 WDL 文件来生成输入模板:
模板格式如下:
当然,如果工作流不是很复杂,也可以按照上面的格式手写 input 文件。下面是一个 GATK 工作流的 input 文件的片段.
实际例子
使用 GATK 构建的Jointcalling
workflow定义
掌握以上几点,就可以理解一个wdl
脚本的整体框架了。在实际使用中,我们只要能理解整个workflow
的流向,会使用wdl
脚本就可以了。 运行wdl
脚本,需要两个文件
- cromwell.jar
- womtools.jar
最新版的下载链接如下:
https://github.com/broadinstitute/cromwell/releases/tag/31
第一步是得到输入参数的列表,用法如下
java -jar womtools.jar inputs myWorkflow.wdl > myWorkflow_inputs.json
用json
格式存存储,这一步得到的只是一个模板,需要编辑这个文件,将对应的参数替换成实际的参数 第二步运行脚本,用法如下
java -jar Cromwell.jar run myWorkflow.wdl —inputs myWorkflow_inputs.json
reference:
https://developer.aliyun.com/article/716546
https://cloud.tencent.com/developer/article/1626274