Skip to content

Commit 72065a4

Browse files
Update hello_nf-core training for nf-core 3.5.2 (#896)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0dc1f1c commit 72065a4

File tree

101 files changed

+1805
-1019
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+1805
-1019
lines changed

.devcontainer/local-features/uv-tools/install.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
# Install python cli tools using uv
44

55
uv tool install pre-commit
6-
uv tool install nf-core==3.4.1
6+
uv tool install nf-core==3.5.2
77
uv tool install "mkdocs-quiz>=1.5.2"

.editorconfig

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,19 @@ indent_size = 2
1515
[*.{py,md}]
1616
indent_style = unset
1717

18-
# ignore nf-core files
18+
# ignore nf-core generated files
1919
[side-quests/solutions/nf-core/**]
2020
charset = unset
2121
end_of_line = unset
2222
insert_final_newline = unset
2323
trim_trailing_whitespace = false
2424
indent_style = unset
2525
indent_size = unset
26+
27+
[hello-nf-core/solutions/core-hello-*/**]
28+
charset = unset
29+
end_of_line = unset
30+
insert_final_newline = unset
31+
trim_trailing_whitespace = false
32+
indent_style = unset
33+
indent_size = unset

.pre-commit-config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ repos:
1919
- id: trailing-whitespace
2020
exclude_types:
2121
- svg
22+
exclude: ^hello-nf-core/solutions/core-hello-.*/
2223
- id: end-of-file-fixer
2324
exclude_types:
2425
- svg
26+
exclude: ^hello-nf-core/solutions/core-hello-.*/

.prettierignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@ side-quests/solutions/nf-core/**/*.yaml
1212
side-quests/solutions/nf-core/**/*.json
1313
side-quests/solutions/nf-core/**/*.html
1414
side-quests/solutions/nf-core/**/*.yml
15+
16+
# Ignore nf-core pipeline solutions (generated by nf-core tools, not hand-authored)
17+
hello-nf-core/solutions/core-hello-*/**

docs/en/docs/hello_nf-core/02_rewrite_hello.md

Lines changed: 95 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ Once the TUI closes, you should see the following console output.
7171
| \| | \__, \__/ | \ |___ \`-._,-`-,
7272
`._,._,'
7373

74-
nf-core/tools version 3.4.1 - https://nf-co.re
74+
nf-core/tools version 3.5.2 - https://nf-co.re
7575

7676

7777
INFO Launching interactive nf-core pipeline creation tool.
@@ -229,7 +229,7 @@ If you look inside the `main.nf` file, you'll see it imports a workflow called `
229229

230230
This is equivalent to the `workflows/demo.nf` workflow we encountered in Part 1, and serves as a placeholder workflow for our workflow of interest, with some nf-core functionality already in place.
231231

232-
```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="15 17 19 35"
232+
```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="15 17 19 53"
233233
/*
234234
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
235235
IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS
@@ -255,7 +255,25 @@ workflow HELLO {
255255
//
256256
// Collate and save software versions
257257
//
258-
softwareVersionsToYAML(ch_versions)
258+
def topic_versions = Channel.topic("versions")
259+
.distinct()
260+
.branch { entry ->
261+
versions_file: entry instanceof Path
262+
versions_tuple: true
263+
}
264+
265+
def topic_versions_string = topic_versions.versions_tuple
266+
.map { process, tool, version ->
267+
[ process[process.lastIndexOf(':')+1..-1], " ${tool}: ${version}" ]
268+
}
269+
.groupTuple(by:0)
270+
.map { process, tool_versions ->
271+
tool_versions.unique().sort()
272+
"${process}:\n${tool_versions.join('\n')}"
273+
}
274+
275+
softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file))
276+
.mix(topic_versions_string)
259277
.collectFile(
260278
storeDir: "${params.outdir}/pipeline_info",
261279
name: 'hello_software_' + 'versions.yml',
@@ -285,6 +303,13 @@ Compared to a basic Nextflow workflow like the one developed in [Hello Nextflow]
285303

286304
These are optional features of Nextflow that make the workflow **composable**, meaning that it can be called from within another workflow.
287305

306+
!!! note "The `Channel.topic` block"
307+
308+
You may have noticed the `def topic_versions = Channel.topic("versions")` block starting at line 17.
309+
This is boilerplate housekeeping code that collects software version information from all modules automatically.
310+
nf-core is rolling out this mechanism across all pipelines in 2026, so you'll see it in all new pipelines going forward.
311+
Part 4 of this course explains how it works in detail.
312+
288313
!!! note "Composable workflows in depth"
289314

290315
The [Workflows of Workflows](../side_quests/workflows_of_workflows.md) Side Quest explores workflow composition in much greater depth, including how to compose multiple workflows together and manage complex data flows between them. We're introducing composability here because it's a fundamental requirement of the nf-core template architecture, which uses nested workflows to organize pipeline initialization, the main analysis workflow, and completion tasks into separate, reusable components.
@@ -696,7 +721,25 @@ workflow HELLO {
696721
//
697722
// Collate and save software versions
698723
//
699-
softwareVersionsToYAML(ch_versions)
724+
def topic_versions = Channel.topic("versions")
725+
.distinct()
726+
.branch { entry ->
727+
versions_file: entry instanceof Path
728+
versions_tuple: true
729+
}
730+
731+
def topic_versions_string = topic_versions.versions_tuple
732+
.map { process, tool, version ->
733+
[ process[process.lastIndexOf(':')+1..-1], " ${tool}: ${version}" ]
734+
}
735+
.groupTuple(by:0)
736+
.map { process, tool_versions ->
737+
tool_versions.unique().sort()
738+
"${process}:\n${tool_versions.join('\n')}"
739+
}
740+
741+
softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file))
742+
.mix(topic_versions_string)
700743
.collectFile(
701744
storeDir: "${params.outdir}/pipeline_info",
702745
name: 'hello_software_' + 'versions.yml',
@@ -717,7 +760,9 @@ workflow HELLO {
717760
*/
718761
```
719762

720-
Overall this code does very little aside from some housekeeping that has to do with capturing the version of any software tools that get run in the pipeline.
763+
The highlighted lines define the composable workflow structure: `workflow HELLO {`, `take:`, `main:`, and `emit:`.
764+
The large block between lines 17–34 is more substantial: it handles software version capture using topic channels, a mechanism nf-core is rolling out across all pipelines in 2026.
765+
We'll explain it in Part 4; for now, treat it as boilerplate that you can leave untouched.
721766

722767
We need to add the relevant code from the composable version of the original workflow that we developed in section 2.
723768

@@ -730,7 +775,8 @@ We're going to tackle this in the following stages:
730775

731776
!!! note
732777

733-
We're going to ignore the version capture for this first pass and will look at how to wire that up in a later part of this training.
778+
We're going to ignore the version capture block for this first pass.
779+
Part 4 explains how it works.
734780

735781
### 3.1. Copy the modules and set up module imports
736782

@@ -877,7 +923,25 @@ This ordering makes sense because in a real pipeline, the processes would emit v
877923
//
878924
// Collate and save software versions
879925
//
880-
softwareVersionsToYAML(ch_versions)
926+
def topic_versions = Channel.topic("versions")
927+
.distinct()
928+
.branch { entry ->
929+
versions_file: entry instanceof Path
930+
versions_tuple: true
931+
}
932+
933+
def topic_versions_string = topic_versions.versions_tuple
934+
.map { process, tool, version ->
935+
[ process[process.lastIndexOf(':')+1..-1], " ${tool}: ${version}" ]
936+
}
937+
.groupTuple(by:0)
938+
.map { process, tool_versions ->
939+
tool_versions.unique().sort()
940+
"${process}:\n${tool_versions.join('\n')}"
941+
}
942+
943+
softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file))
944+
.mix(topic_versions_string)
881945
.collectFile(
882946
storeDir: "${params.outdir}/pipeline_info",
883947
name: 'hello_software_' + 'versions.yml',
@@ -906,7 +970,25 @@ This ordering makes sense because in a real pipeline, the processes would emit v
906970
//
907971
// Collate and save software versions
908972
//
909-
softwareVersionsToYAML(ch_versions)
973+
def topic_versions = Channel.topic("versions")
974+
.distinct()
975+
.branch { entry ->
976+
versions_file: entry instanceof Path
977+
versions_tuple: true
978+
}
979+
980+
def topic_versions_string = topic_versions.versions_tuple
981+
.map { process, tool, version ->
982+
[ process[process.lastIndexOf(':')+1..-1], " ${tool}: ${version}" ]
983+
}
984+
.groupTuple(by:0)
985+
.map { process, tool_versions ->
986+
tool_versions.unique().sort()
987+
"${process}:\n${tool_versions.join('\n')}"
988+
}
989+
990+
softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file))
991+
.mix(topic_versions_string)
910992
.collectFile(
911993
storeDir: "${params.outdir}/pipeline_info",
912994
name: 'hello_software_' + 'versions.yml',
@@ -947,15 +1029,15 @@ Finally, we need to update the `emit` block to include the declaration of the wo
9471029

9481030
=== "After"
9491031

950-
```groovy title="core-hello/workflows/hello.nf" linenums="55" hl_lines="2"
1032+
```groovy title="core-hello/workflows/hello.nf" linenums="69" hl_lines="2"
9511033
emit:
9521034
cowpy_hellos = cowpy.out
9531035
versions = ch_versions // channel: [ path(versions.yml) ]
9541036
```
9551037

9561038
=== "Before"
9571039

958-
```groovy title="core-hello/workflows/hello.nf" linenums="55"
1040+
```groovy title="core-hello/workflows/hello.nf" linenums="69"
9591041
emit:
9601042
versions = ch_versions // channel: [ path(versions.yml) ]
9611043
```
@@ -1143,7 +1225,7 @@ The good news is that our pipeline's needs are much simpler, so we can replace a
11431225

11441226
As a reminder, this is what the channel construction looked like (as seen in the solutions directory):
11451227

1146-
```groovy title="solutions/composable-hello/main.nf" linenums="10" hl_lines="4"
1228+
```groovy title="solutions/composable-hello/main.nf" linenums="10" hl_lines="2"
11471229
// create a channel for inputs from a CSV file
11481230
greeting_ch = channel.fromPath(params.greeting)
11491231
.splitCsv()
@@ -1268,7 +1350,7 @@ And while we're at it, let's tighten the default resource limits to ensure this
12681350

12691351
=== "After"
12701352

1271-
```groovy title="core-hello/config/test.config" linenums="13" hl_lines="3-4"
1353+
```groovy title="core-hello/conf/test.config" linenums="13" hl_lines="3-4"
12721354
process {
12731355
resourceLimits = [
12741356
cpus: 2,
@@ -1280,7 +1362,7 @@ And while we're at it, let's tighten the default resource limits to ensure this
12801362

12811363
=== "Before"
12821364

1283-
```groovy title="core-hello/config/test.config" linenums="13" hl_lines="3-4"
1365+
```groovy title="core-hello/conf/test.config" linenums="13" hl_lines="3-4"
12841366
process {
12851367
resourceLimits = [
12861368
cpus: 4,

0 commit comments

Comments
 (0)