第三讲:配置文件的骨架——参数、作用域、环境变量与包含
配置文件(nextflow.config)是Nextflow管线的中枢神经系统。说白了,所有运行时的行为都靠它来统一管控。我当初第一次写Nextflow管线时,把所有配置都堆在一个文件里,可想而知,后来部署到不同环境时有多痛苦。
这一讲,我们就把配置文件的底裤扒清楚。搞懂参数、作用域、环境变量和文件包含这四样东西,你写出来的配置就能像乐高一样灵活拼接。
3.1 参数定义:最基础的赋值
参数是配置文件里的最小单元。你定义一个变量,给个值,就完了。
params.input = "/data/samples/*.fastq"
params.outdir = "results"
params.genome = "GRCh38"
这很简单,对吧?但我要提醒你一个坑:params 是 Nextflow 预留的关键字。所有你在命令行里通过 --input 传入的参数,都会自动映射到 params.input。所以,千万别把 params 当作普通变量来用。
3.2 作用域:给参数分门别类
作用域就像文件夹。你把不同来源、不同用途的参数放进不同的“抽屉”里。Nextflow 支持的作用域有十几种,但日常我们最常用的是这几个:
| 作用域 | 用途 | 我遇到的典型场景 |
|---|---|---|
| params | 公开参数,用户和命令行可覆盖 | 输入路径、输出路径、阈值 |
| process | 进程的默认资源与指令 | cpus、memory、container |
| executor | 执行器配置 | queue、queueSize、retry |
| env | 环境变量 | PATH、TMPDIR、API_KEY |
| manifest | 管线的元信息 | name、author、version |
举个例子,你想给某个进程单独分配更多内存,可以这样写:
process {
withName: 'star_alignment' {
cpus = 16
memory = '64.GB'
}
withName: 'fastqc' {
cpus = 2
memory = '4.GB'
}
}
作用域的优先级
你想想看,如果同一个参数在多个地方定义了,听谁的?Nextflow 有一套固定的覆盖规则:
- 命令行参数(最高优先级)
- profile 中的配置
- 基础配置文件中的配置
- params 默认值(最低优先级)
我在项目中遇到过最典型的问题:一个同事在 profile 里定义了一个参数,又在基础配置里定义了另一个,结果两个地方的值相互覆盖,最后跑出来的结果完全不对。排查了半天——就是因为作用域优先级没搞清。
3.3 环境变量:让配置与环境解耦
密码、Token、路径前缀……这些东西不该硬编码在配置文件里。怎么办?用环境变量。
在配置文件中读取环境变量
env {
// 直接读取系统环境变量
NXF_HOME = "$HOME/.nextflow"
TMPDIR = "/scratch"
}
// 在参数中使用环境变量
params.api_token = System.getenv('API_TOKEN') ?: 'default_token'
params.aws_bucket = "${System.getenv('AWS_S3_BUCKET')}/project1"
这段代码里有个技巧:System.getenv() 是 Groovy 的方法,可以在配置文件中直接调用。如果环境变量不存在,就用 ?: 给个默认值。这样你的配置文件在本地开发和服务器部署时都能正常工作,只是行为略有不同。
在进程级别设置环境变量
process {
withName: 'run_secret_tool' {
env {
API_KEY = "sk-xxxx"
ENDPOINT = "https://api.example.com"
}
script:
"""
secret_tool --endpoint \$ENDPOINT
"""
}
}
你可能会奇怪,为什么环境变量不在外面统一定义?嗯,要是每个进程需要的环境变量不同,那你写在全局 env 作用域里就不合适了。每个进程单独定义,清晰又安全。
3.4 配置文件包含:把大文件拆成小模块
一个 nextflow.config 文件,动辄几百行。所有人都改这一个文件,冲突是迟早的事。我建议你把它拆成多个小文件,然后用 includeConfig 来组装。
基础用法
// 主配置文件 nextflow.config
includeConfig 'configs/params.config'
includeConfig 'configs/process.config'
includeConfig 'configs/executor.config'
// 环境相关的配置,根据 profile 条件包含
profiles {
standard {
includeConfig 'configs/standard.env'
}
cluster {
includeConfig 'configs/cluster.env'
}
}
这样一拆,每个人只关心自己那一亩三分地。管参数的人改 params.config,管资源的人改 process.config,互不干扰。
路径解析的陷阱
这里有个容易踩坑的地方:includeConfig 的路径是相对于当前配置文件所在目录的。如果你的主文件在根目录,被包含的文件在 configs/ 子目录里,那路径就是 configs/xxx.config。但如果被包含的文件里又包含另一个文件,路径就要特别注意了。
// 假设主文件在 pipeline/nextflow.config
includeConfig 'configs/common.config'
// 而 common.config 在 pipeline/configs/common.config
// 它里面想包含其他文件,路径是相对于它自己所在的目录
includeConfig 'tools.config' // 会去找 pipeline/configs/tools.config
3.5 一张图搞定配置结构
上面讲的这些关系,用一张图来表示更直观:
这张图应该能帮你理清关系了。主配置文件就像总开关,通过 includeConfig 拉取各个模块;params、process、env 这三层作用域各自管好自己的事;最后通过优先级规则决定谁说了算。
3.6 小结
参数、作用域、环境变量、配置文件包含——这四个东西是 nextflow.config 的四大支柱。你用好了,配置文件就能做到:
- 参数清晰:每个人都清楚哪些值是可以调的,哪些是内部固定的
- 作用域隔离:进程配置、执行器配置、管道参数互不污染
- 环境解耦:敏感信息和环境差异通过环境变量传递,配置本身保持干净
- 模块化:不同角色的维护者只关心自己的小文件,减少冲突