Files
drug-discovery-prompts/upstream/K-Dense-AI-scientific-agent-skills/skills/nextflow/references/language.md

341 lines
15 KiB
Markdown

---
title: "Nextflow Language (DSL2)"
task: ""
lineage_type: import
upstream_source: https://github.com/K-Dense-AI/scientific-agent-skills/blob/9c9bd2e9/skills/nextflow/references/language.md
upstream_sha: 9c9bd2e9
imported_at: 2026-06-27
prompt_class: prompt
upstream_changes: accepted
author: upstream
validated: false
---
# Nextflow Language (DSL2)
The complete Nextflow scripting language: processes, channels, operators, workflows, and modules. Nextflow is a Groovy-based DSL; DSL2 is the default and only DSL (DSL1 is removed, so `nextflow.enable.dsl=2` is unnecessary). Source: https://www.nextflow.io/docs/latest/
**Current syntax conventions** (a strict-syntax parser, `NXF_SYNTAX_PARSER=v2`, is opt-in in 25.x and becomes the default in **26.04** — write to it now, it also runs on the legacy parser):
- **`channel.of(...)`** (lowercase namespace) is canonical; `Channel.of(...)` still works but is discouraged.
- **Explicit closure parameters** (`{ v -> v * 2 }`) are preferred over the implicit `it`.
- Name process outputs with **`emit:`**; scale resources with **`task.attempt`**.
- **`output {}` + `publish:`** (full feature in 25.10) is the new declarative way to publish results; `publishDir` still works and remains dominant in nf-core — both are shown below.
- Avoid removed/deprecated idioms: `for`/`while` loops (use `each`/`collect`), `import`/custom `class` (use functions or `lib/`), `process shell:` (use `script:`), `include … addParams()`.
## Table of Contents
- [Script structure](#script-structure)
- [Processes](#processes)
- [Process directives](#process-directives)
- [Channels](#channels)
- [Operators](#operators)
- [Workflows](#workflows)
- [Modules](#modules)
- [Dynamic resources and error handling](#dynamic-resources-and-error-handling)
- [Groovy essentials and gotchas](#groovy-essentials-and-gotchas)
## Script structure
A Nextflow script (`.nf`) mixes process/workflow definitions with Groovy. Every script enables DSL2 implicitly (it is the default since 22.03). A run begins at the **unnamed `workflow {}`** block (the entry workflow).
```nextflow
#!/usr/bin/env nextflow
params.input = 'data/*.fastq' // pipeline parameter with a default
process FASTQC { /* ... */ } // a process definition
workflow { // entry point
reads = channel.fromPath(params.input)
FASTQC(reads)
}
```
Run with `nextflow run main.nf --input 'data/*.fastq'`. Parameters declared as `params.x` are overridable on the CLI (`--x`), in `nextflow.config`, or in a `-params-file`.
## Processes
A `process` defines a task: a (usually Bash) script executed in its own isolated **work directory**. Nextflow stages declared inputs in and declared outputs out, so processes never read/write each other's files directly — they communicate only through channels.
```nextflow
process ALIGN {
tag "$meta.id" // label shown in the log/trace
label 'process_high' // maps to resources in config
container 'quay.io/biocontainers/bwa:0.7.17--hed695b0_7'
publishDir "${params.outdir}/bam", mode: 'copy'
input:
tuple val(meta), path(reads) // a sample: metadata map + file(s)
path index // a shared reference (value channel)
output:
tuple val(meta), path("*.bam"), emit: bam
path "versions.yml", emit: versions
when:
meta.run_alignment != false // skip task if false
script:
def prefix = task.ext.prefix ?: meta.id
def args = task.ext.args ?: '' // extra flags injected from config
"""
bwa mem $args -t $task.cpus $index ${reads} | samtools sort -o ${prefix}.bam
cat <<-END_VERSIONS > versions.yml
"${task.process}":
bwa: \$(bwa 2>&1 | sed -n 's/Version: //p')
END_VERSIONS
"""
stub:
"""
touch ${meta.id}.bam
touch versions.yml
"""
}
```
### Inputs
Declared one per line under `input:`. Each input consumes one item from a channel.
| Qualifier | Meaning |
|-----------|---------|
| `val(x)` | Any value (string, number, map) |
| `path(f)` | A file/dir; staged into the work dir. Use `path` (not the old `file`) |
| `tuple val(meta), path(reads)` | A composite item — the nf-core standard: a meta map + files |
| `env(NAME)` | Value exposed as an environment variable |
| `stdin` | Feed the channel item to the script's stdin |
| `each x` | Repeat the process once per value in `x` (combinatorial) |
Inputs are positional and matched to channels in call order: `ALIGN(reads_ch, index_ch)`.
### Outputs
Declared under `output:`; each becomes a channel. Use `emit:` to name outputs so callers can reference `ALIGN.out.bam` instead of positional `ALIGN.out[0]`.
| Form | Meaning |
|------|---------|
| `path "*.bam"` | Files matched by glob in the work dir after the script runs |
| `tuple val(meta), path("*.bam")` | Carry metadata forward with the file |
| `val x` | Emit a value computed in the process |
| `stdout` | Capture the script's stdout as the output |
| `eval('cmd')` | Capture the stdout of a command run in the task env (24.04+) — used for tool versions |
| `path "out", emit: name` | Named output channel (reference as `PROC.out.name`) |
| `..., topic: versions` | Also route this output to a named topic channel |
| `optional true` | Output may be absent without erroring |
### Script, shell, exec
- **`script:`** (default) — a multi-line string run as Bash. Nextflow variables interpolate with `$var`/`${expr}`; escape shell vars you do NOT want Nextflow to touch as `\$var`.
- **`shell:`** — like script but Nextflow vars use `!{var}`, leaving `$` for the shell. **Deprecated as of 25.04** — use `script:` with `\$` to escape shell vars.
- **`exec:`** — native Groovy, no external process (for in-line computation).
- A process can run any interpreter via a shebang (e.g. `#!/usr/bin/env python`).
```nextflow
process PY {
input: val x
output: stdout
script:
"""
#!/usr/bin/env python
print(${x} ** 2)
"""
}
```
### Conditional execution
- `when:` — skip the task when the expression is false (prefer filtering channels upstream when possible).
- A `script:` can branch with normal Groovy `if/else` returning different command strings.
- `stub:` — an alternate minimal script run with `-stub-run` to test pipeline wiring without the real tool. nf-core requires stubs.
## Process directives
Set inside a process (or globally via config). Most-used:
| Directive | Purpose |
|-----------|---------|
| `cpus`, `memory`, `time`, `disk` | Resource requests (e.g. `memory '8.GB'`, `time '2.h'`) |
| `container` | Container image for this process |
| `conda` | Conda packages/env for this process |
| `publishDir` | Copy/link outputs to a results dir (`mode: 'copy'|'symlink'|'link'`) |
| `tag` | Human-readable label per task in logs/trace |
| `label` | Group processes (target with `withLabel:` in config) |
| `errorStrategy` | `'terminate'` (default), `'ignore'`, `'retry'`, `'finish'` |
| `maxRetries`, `maxErrors` | Retry limits |
| `cache` | `true`/`'lenient'`/`'deep'`/`false` — caching behavior |
| `scratch` | Run in node-local scratch then stage out |
| `stageInMode`/`stageOutMode` | `'symlink'`/`'copy'`/`'link'` staging |
| `beforeScript`/`afterScript` | Commands wrapping the task script |
| `accelerator` | GPU request (e.g. `accelerator 1, type: 'nvidia-tesla-v100'`) |
| `array` | Submit as a job array (HPC/cloud), e.g. `array 100` |
| `ext` | Free-form map (`ext.args`, `ext.prefix`) injected from config |
| `pod` | Kubernetes pod options |
| `module` | Load an HPC environment module |
| `maxForks` | Cap parallel tasks for this process |
Access the resolved values at runtime via `task.*` (`task.cpus`, `task.memory`, `task.attempt`, `task.process`, `task.ext.args`).
## Channels
Channels are the asynchronous queues connecting processes. Two kinds:
- **Queue channel**: an ordered, *consumable* stream of items. Produced by most factories/operators and by process outputs. Can be consumed once.
- **Value channel** (singleton): holds one value that can be read an unlimited number of times. Created by `channel.value()`, by operators like `collect`/`first`, or implicitly from a single value. A process input bound to a value channel is reused for every task.
### Channel factories
```nextflow
channel.of(1, 2, 3) // emit given values (ranges expand: 1..23)
channel.fromList([1, 2, 3]) // emit list items
channel.value('ref.fa') // singleton value channel
channel.fromPath('data/*.bam') // one item per matching file
channel.fromPath('data/**.fastq', checkIfExists: true) // also: arity:'1', type:'file', hidden:true
channel.fromFilePairs('data/*_{1,2}.fastq.gz') // -> [id, [r1, r2]] for paired reads
channel.topic('versions') // collect values emitted to a named topic (24.04+)
channel.empty() // emits nothing
```
> `channel.fromSRA(...)` exists but is deprecated as of 26.04 — prefer a **samplesheet** (`splitCsv`) over fetching reads by accession.
`fromFilePairs` is the idiomatic way to group paired-end reads; it yields `[ sampleId, [read1, read2] ]`, which you typically `map` into the nf-core `[ meta, [reads] ]` shape.
## Operators
Operators transform/combine channels. Chain with `.`; the dataflow graph is built from these connections.
| Operator | Purpose |
|----------|---------|
| `map { }` | Transform each item |
| `filter { }` | Keep items matching a condition/type/regex |
| `flatten` | Flatten nested emissions into individual items |
| `collect` | Gather all items into a single list (→ value channel) |
| `toList` / `toSortedList` | Collect into one (sorted) list |
| `groupTuple` | Group tuples by key (e.g. by `meta`) — often needs `groupTuple(by: 0)` |
| `join` | Inner-join two channels by a matching key |
| `combine` | Cartesian product (optionally `by:` a key) |
| `cross` | Combine matching keyed items |
| `mix` | Merge multiple channels into one stream |
| `concat` | Emit one channel fully, then the next, in order |
| `branch { }` | Route items into multiple named sub-channels by condition |
| `multiMap { }` | Emit to several channels from one pass |
| `splitCsv` / `splitText` / `splitFasta` / `splitFastq` | Split file contents into items |
| `collectFile` | Write items into one or more files |
| `unique` / `distinct` | De-duplicate |
| `first` / `last` / `take` / `until` | Select subsets |
| `set { ch }` | Name the resulting channel (alternative to `ch =`) |
| `view { }` | Print items for debugging (returns the channel unchanged) |
| `ifEmpty` | Provide a default if the channel is empty |
| `dump(tag:'x')` | Debug-print when run with `-dump-channels x` |
```nextflow
// Build the nf-core [meta, reads] shape from a samplesheet
channel
.fromPath(params.input)
.splitCsv(header: true)
.map { row -> tuple([id: row.sample, single_end: row.fastq_2 ? false : true],
row.fastq_2 ? [file(row.fastq_1), file(row.fastq_2)] : [file(row.fastq_1)]) }
.set { reads_ch }
// Group per-sample results, then join two channels by meta
counts.groupTuple()
.join(metadata) // matches on the first (key) element
.view()
```
## Workflows
A `workflow` composes processes and other workflows. The **unnamed** workflow is the entry point. **Named** workflows are reusable (sub)workflows.
```nextflow
workflow RNASEQ {
take: // typed inputs (channels)
reads
index
main: // pipeline logic
FASTQC(reads)
ALIGN(reads, index)
QUANT(ALIGN.out.bam)
emit: // named outputs
bam = ALIGN.out.bam
counts = QUANT.out.counts
versions = FASTQC.out.versions.mix(ALIGN.out.versions)
}
workflow { // entry: wire inputs and call the named workflow
reads = channel.fromFilePairs(params.reads)
index = channel.value(file(params.index))
RNASEQ(reads, index)
RNASEQ.out.counts.view()
}
```
- Call a process/workflow like a function: `ALIGN(reads, index)`. Outputs are on `.out` (use `emit:` names: `ALIGN.out.bam`).
- A process can only be **called once** per workflow; to reuse it, `include` it again under an alias.
- Pipe syntax works for simple chains: `reads | FASTQC`.
- **Declarative outputs (25.10+)**: assign channels in the entry workflow's `publish:` section and describe them in a top-level `output {}` block — the recommended replacement for the `publishDir` directive (which still works and dominates nf-core):
```nextflow
workflow {
main:
ch = ANALYZE(input)
publish:
results = ch // name the published channel
}
output {
results { path 'analysis' } // -> <outputDir>/analysis (default outputDir: results/)
}
```
## Modules
Modules are `.nf` files whose processes/workflows are imported with `include`. This is the basis of nf-core's reusable components.
```nextflow
include { FASTQC } from './modules/fastqc/main.nf'
include { ALIGN as ALIGN_TUMOR;
ALIGN as ALIGN_NORMAL } from './modules/align/main.nf'
include { RNASEQ } from './subworkflows/rnaseq.nf'
```
- `as` aliases let you include the same component multiple times.
- Includes are resolved relative to the including file; `.nf` extension optional.
- Params should be passed explicitly (as inputs), not read globally inside modules — this keeps modules portable (an nf-core requirement).
## Dynamic resources and error handling
Make pipelines robust by retrying failures with more resources instead of over-provisioning everything. `task.attempt` increments on each retry.
```nextflow
process BIG_JOB {
label 'process_high'
cpus { 4 * task.attempt }
memory { 8.GB * task.attempt }
time { 4.h * task.attempt }
errorStrategy { task.exitStatus in [137, 140, 143] ? 'retry' : 'terminate' }
maxRetries 3
// ...
}
```
- Exit codes 137/140/143 typically mean out-of-memory/walltime kills — retry with more resources.
- `errorStrategy 'ignore'` lets the pipeline continue past a failed task; `'finish'` stops launching new tasks but lets running ones complete.
- In nf-core, resource scaling lives in `conf/base.config` keyed on `process_*` labels (see `references/developing.md`).
## Groovy essentials and gotchas
- Strings: single-quoted are literal; double-quoted interpolate (`"${x}"`). In `script:` blocks, escape shell variables as `\$VAR`.
- Define helper values with `def` inside `script:`/closures to avoid leaking globals.
- Maps use Groovy syntax: `[ id: 'x', single_end: false ]`; access as `meta.id`.
- **Common gotchas**:
- Re-using a consumed **queue** channel yields nothing — use a **value** channel (or `collect`) for things consumed by many tasks (like a reference index).
- `groupTuple` may emit before all items arrive unless sizes are known; provide `size:` or use `groupTuple(by:)` carefully.
- A process called twice without aliasing is an error; `include ... as`.
- Globs in `output:` match the **work directory**, not `publishDir`.
- Prefer filtering channels over `when:` for clarity and caching.
- **Strict syntax / language server**: recent Nextflow ships a VS Code extension + `nextflow lint` and a stricter parser; nf-core is migrating pipelines to it. Keep scripts to documented DSL2 constructs and avoid deprecated DSL1 idioms (`Channel.create()`, `.into{}` overuse, top-level `file()` for inputs).