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。

小提示: 如果不想每次写 docker,可以把 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 工作流的“骨架”。

WDL “Hello RNA-seq” 工作流结构 Input: sample_name workflow hello_rnaseq task hello_print command: echo Output: greeting 工作流串联输入→任务→输出。实际 RNA-seq 会串十几个 task,结构相同。

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 文件的路径和大小。你看,扩展起来就是这么自然。

我为什么喜欢从这个 hello 例子开始? 因为它让你在 5 分钟内熟悉 WDL 的骨架——输入、任务、输出、运行时。后面加上 STAR、samtools、featureCounts 时,框架完全一样,只是 command 变长了。这个基础打牢,后面速度飞起。

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。