4、第一个WDL工作流:快速写一个“Hello RNA-seq”任务
学新东西,我总爱先弄个最小的例子跑起来。WDL 也一样。咱们不急着上全流程,先写一个能打印 “Hello RNA-seq!” 的简单任务。跑通了,你心里就有底了。
说白了,WDL 就两个核心概念:workflow(工作流)和 task(任务)。Workflow 像导演,负责编排任务之间的依赖和数据传递;task 像演员,实际执行具体操作。每个 task 有输入、命令、运行时配置和输出。
4.1 完整的“Hello RNA-seq” WDL
先贴代码,再慢慢拆。你找个目录,新建一个 hello_rnaseq.wdl,把下面的内容拷进去。
version 1.0
workflow hello_rnaseq {
input {
String sample_name = "SRR123456"
}
call hello_print {
input:
name = sample_name
}
output {
String greeting = hello_print.greeting
}
}
task hello_print {
input {
String name
}
command {
echo "Hello RNA-seq! Sample: ${name}"
}
output {
String greeting = read_string(stdout())
}
runtime {
docker: "ubuntu:20.04"
memory: "1 GB"
cpu: 1
}
}
嗯,看着有点长?其实结构很清晰。咱们一块块来看。
4.2 拆解 WDL 结构的四大块
| 模块 | 作用 | 我的经验之谈 |
|---|---|---|
version 1.0 | 声明 WDL 语法版本。没有这一行,Cromwell 可能不认。 | 我一开始老忘写,结果报错一脸懵。后来强迫自己第一行就敲这个。 |
workflow { ... } | 定义工作流的输入、调用哪些 task、以及最终输出。 | 你可以把 workflow 想象成总指挥,它不干活,但决定谁先谁后。 |
task { ... } | 定义具体执行的任务,包括命令、输入输出、运行时资源。 | task 是 WDL 里最常写的部分。我的项目里 task 数比 workflow 多十几倍。 |
runtime { ... } | 指定 docker 镜像、内存、CPU 等。本地跑可以不要,上生产必须配。 | 我有一次忘配 docker,跑在别人的环境里直接报错 “command not found”。 |
4.3 运行你的第一个 WDL
我习惯本地用 miniwdl 快速验证,比 Cromwell 轻量多了。安装很简单:
pip install miniwdl
然后执行:
miniwdl run hello_rnaseq.wdl
如果你没指定输入,miniwdl 会用 workflow 里定义的默认值 "SRR123456"。终端应该输出类似:
Hello RNA-seq! Sample: SRR123456
看到了吧?就是这么简单。你还可以通过命令行覆盖输入:
miniwdl run hello_rnaseq.wdl sample_name="Hello_World"
输出就会变成 Hello_World。
runtime {} 整个删掉,miniwdl 会在宿主机直接跑命令。但注意,真正跑 RNA-seq 流程时,依赖的工具(比如 STAR、samtools)都装在镜像里,所以 docker 是必须的。
4.4 理解 input / output 的数据传递
你可能会问:为什么 task 里输出 String greeting,workflow 里又声明了一个 output { String greeting = hello_print.greeting }?说白了,这是一种“显式传递”。
- Task 内部输出: 通过
read_string(stdout())抓取标准输出,变成一个 WDL 变量。 - Workflow 输出: 把 task 的变量“暴露”给外部,这样下游工作流或用户可以拿到最终结果。
我个人刚开始经常搞混,以为直接在 task 里输出就行,结果 workflow 输出是空的。记得要“二次声明”。
4.5 一张图看清 WDL 的执行流程
下面这张 SVG 图总结了咱们这个 hello 工作流的“骨架”。
4.6 常见坑与避坑指南
- 路径问题: WDL 默认所有文件路径相对于工作目录。如果你在 task 里写
/home/data/xxx,docker 里可能找不到。我建议所有输入文件都用 WDL 的File类型声明,让引擎帮你处理挂载。 - Docker 没写标签:
ubuntu:latest这种会导致每次拉取不同版本,结果不可复现。我吃过一次亏后,现在全用精确版本号。 - 输出没写 read_string: 如果你在 task output 里直接写
String greeting = stdout(),Cromwell 会报类型不匹配。得用read_string(stdout())把文件内容读成字符串。
4.7 让你的工作流也支持输入文件
光打印字符串显然离真正的 RNA-seq 差得远。那咱们把 hello 升级一下,让它能接受一个 FASTQ 文件 并打印文件名。
version 1.0
workflow hello_fastq {
input {
File fastq
}
call print_fastq_name {
input:
fq = fastq
}
}
task print_fastq_name {
input {
File fq
}
command {
echo "Input FASTQ: ${fq}"
ls -lh ${fq}
}
output {
String file_info = read_string(stdout())
}
runtime {
docker: "ubuntu:20.04"
}
}
跑的时候需要给一个 JSON 输入文件:
echo '{"hello_fastq.fastq": "/path/to/your/test.fastq"}' > inputs.json
miniwdl run hello_fastq.wdl -i inputs.json
输出里会显示 FASTQ 文件的路径和大小。你看,扩展起来就是这么自然。
4.8 用 Cromwell 跑一遍
如果你已经装好了 Cromwell(Java 环境),可以用命令行提交:
java -jar cromwell.jar run hello_rnaseq.wdl
# 或者指定输入 JSON
java -jar cromwell.jar run hello_rnaseq.wdl --inputs inputs.json
Cromwell 的输出更详细,会生成 cromwell-outputs/ 文件夹,里面包含每个 task 的 stdout/stderr 和最终输出文件。我个人用 Cromwell 跑生产流程,用 miniwdl 做快速验证。
好了,第一个 WDL 工作流到此结束。拿去跑一下试试?当你看到终端出现那句 “Hello RNA-seq! Sample: SRR123456” 时,相信我,那种成就感会让你迫不及待想写下一个 task。