Ir para o conteúdo

Parte 2: Reescrever Hello para nf-core

Tradução assistida por IA - saiba mais e sugira melhorias

Nesta segunda parte do curso de treinamento Hello nf-core, mostramos como criar uma versão compatível com nf-core do pipeline produzido pelo curso para iniciantes Hello Nextflow.

Vamos fazer isso em duas fases: primeiro, usaremos as ferramentas nf-core para criar uma estrutura de pipeline, e então enxertaremos o código do pipeline 'regular' existente nessa estrutura.

Se você não está familiarizado com o pipeline Hello ou precisa relembrar, consulte esta página de informações.

Dica

Esta parte do curso apresentará dois mecanismos importantes do Nextflow que não são abordados no curso introdutório Hello Nextflow: meta maps e workflows of workflows, ambos cobertos em detalhes nas Side Quests vinculadas.

As instruções abaixo incluem as informações essenciais que você precisa para entender como esses mecanismos são usados no contexto nf-core, mas pode ser muita coisa para absorver de uma vez. Se você tiver tempo, recomendamos trabalhar primeiro nas duas Side Quests (em qualquer ordem):

Nota

Certifique-se de estar no diretório hello-nf-core no seu terminal.


1. Examinar a estrutura do código do pipeline

O projeto nf-core impõe diretrizes rigorosas sobre como os pipelines são estruturados e como o código é organizado, configurado e documentado.

Antes de começarmos nosso projeto de criação de pipeline, precisamos entender essa estrutura e organização. Então vamos dar uma olhada em como o código do pipeline está organizado no repositório nf-core/demo, usando o link simbólico pipelines que criamos na Parte 1.

Como lembrete, você pode usar tree ou o explorador de arquivos para encontrar e abrir o diretório nf-core/demo.

tree -L 1 pipelines/nf-core/demo
Conteúdo do diretório
pipelines/nf-core/demo
├── assets
├── CHANGELOG.md
├── CITATIONS.md
├── CODE_OF_CONDUCT.md
├── conf
├── docs
├── LICENSE
├── main.nf
├── modules
├── modules.json
├── nextflow.config
├── nextflow_schema.json
├── nf-test.config
├── README.md
├── ro-crate-metadata.json
├── subworkflows
├── tests
├── tower.yml
└── workflows

Por enquanto, vamos focar especificamente nos componentes de código do pipeline (main.nf, workflows, subworkflows, modules) e em como eles se relacionam entre si.

1.1. Estrutura modular dos fluxos de trabalho nf-core

A organização padrão do código de pipelines nf-core segue uma estrutura modular projetada para maximizar a reutilização de código, conforme introduzido em Hello Modules, Parte 4 do curso Hello Nextflow, embora no estilo nf-core isso seja implementado com um pouco mais de complexidade. Especificamente, os pipelines nf-core fazem uso abundante de subfluxos de trabalho, ou seja, scripts de fluxo de trabalho que são importados por um fluxo de trabalho pai.

Isso pode soar um pouco abstrato, então vamos ver como isso é usado na prática no pipeline nf-core/demo.

Se você olhar dentro do arquivo main.nf, verá que ele importa um fluxo de trabalho chamado DEMO de workflows/demo.nf, bem como alguns módulos e subfluxos de trabalho.

Veja como são as relações entre os componentes de código relevantes:

subworkflows/workflows/demo.nffastqc/main.nfmultiqc/main.nfseqtk/trim/main.nfmain.nfincludeincludemodules/nf-core/local/utils_nfcore_demo_pipeline/main.nfnf-core/utils_*/main.nf

O fluxo de trabalho sem nome em main.nf é chamado de script entrypoint. Ele atua como um wrapper para dois tipos de fluxos de trabalho aninhados: o fluxo de trabalho DEMO contendo a lógica de análise real, localizado em workflows/demo.nf, e um conjunto de fluxos de trabalho de manutenção localizados em subworkflows/. O fluxo de trabalho demo.nf chama módulos localizados em modules/; estes contêm os processos que realizarão as etapas de análise reais.

Nota

Subfluxos de trabalho não se limitam a funções de manutenção, e podem fazer uso de módulos de processo.

O pipeline nf-core/demo mostrado aqui é relativamente simples no espectro, mas outros pipelines nf-core (como nf-core/rnaseq) utilizam subfluxos de trabalho que participam da análise real.

Agora, vamos revisar esses componentes em detalhes.

1.2. O script entrypoint: main.nf

O script main.nf é o entrypoint a partir do qual o Nextflow começa quando executamos nextflow run nf-core/demo. Isso significa que quando você executa nextflow run nf-core/demo para rodar o pipeline, o Nextflow encontra e executa automaticamente o script main.nf. Isso funciona para qualquer pipeline Nextflow que siga essa nomenclatura e estrutura convencionais, não apenas pipelines nf-core.

Usar um script entrypoint facilita a execução de subfluxos de trabalho padronizados de 'manutenção' antes e depois da execução do script de análise real. Vamos revisar esses subfluxos depois de examinarmos o fluxo de trabalho de análise real e seus módulos.

1.3. O script de análise: workflows/demo.nf

O fluxo de trabalho workflows/demo.nf é onde a lógica central do pipeline está armazenada. Ele é estruturado de forma semelhante a um fluxo de trabalho Nextflow normal, exceto que é projetado para ser chamado de um fluxo de trabalho pai, o que requer alguns recursos extras. Abordaremos as diferenças relevantes na próxima parte deste curso, quando tratarmos da conversão do pipeline Hello simples do Hello Nextflow para uma forma compatível com nf-core.

O fluxo de trabalho demo.nf chama módulos localizados em modules/, que revisaremos a seguir.

Nota

Alguns fluxos de trabalho de análise nf-core exibem níveis adicionais de aninhamento ao chamar subfluxos de trabalho de nível inferior. Isso é usado principalmente para agrupar dois ou mais módulos comumente usados juntos em segmentos de pipeline facilmente reutilizáveis. Você pode ver alguns exemplos navegando pelos subfluxos de trabalho nf-core disponíveis no site nf-core.

Quando o script de análise usa subfluxos de trabalho, eles são armazenados no diretório subworkflows/.

1.4. Os módulos

Os módulos são onde o código dos processos reside, conforme descrito na Parte 4 do curso de treinamento Hello Nextflow.

No projeto nf-core, os módulos são organizados usando uma estrutura aninhada em múltiplos níveis que reflete tanto sua origem quanto seu conteúdo. No nível superior, os módulos são diferenciados como nf-core ou local (não fazendo parte do projeto nf-core), e então colocados em um diretório nomeado de acordo com a(s) ferramenta(s) que encapsulam. Se a ferramenta pertence a um toolkit (ou seja, um pacote contendo múltiplas ferramentas), há um nível de diretório intermediário nomeado de acordo com o toolkit.

Você pode ver isso aplicado na prática nos módulos do pipeline nf-core/demo:

tree -L 3 pipelines/nf-core/demo/modules
Conteúdo do diretório
pipelines/nf-core/demo/modules
└── nf-core
    ├── fastqc
    │   ├── environment.yml
    │   ├── main.nf
    │   ├── meta.yml
    │   └── tests
    ├── multiqc
    │   ├── environment.yml
    │   ├── main.nf
    │   ├── meta.yml
    │   └── tests
    └── seqtk
        └── trim

7 directories, 6 files

Aqui você vê que os módulos fastqc e multiqc ficam no nível superior dentro dos módulos nf-core, enquanto o módulo trim fica sob o toolkit ao qual pertence, seqtk. Neste caso não há módulos local.

O arquivo de código do módulo que descreve o processo é sempre chamado main.nf, e é acompanhado por testes e arquivos .yml que ignoraremos por enquanto.

Em conjunto, o fluxo de trabalho entrypoint, o fluxo de trabalho de análise e os módulos são suficientes para executar as partes 'interessantes' do pipeline. No entanto, sabemos que também há subfluxos de trabalho de manutenção, então vamos examiná-los agora.

1.5. Os subfluxos de trabalho de manutenção

Assim como os módulos, os subfluxos de trabalho são diferenciados em diretórios local e nf-core, e cada subfluxo de trabalho tem sua própria estrutura de diretório aninhada com seu próprio script main.nf, testes e arquivo .yml.

tree -L 3 pipelines/nf-core/demo/subworkflows
Conteúdo do diretório
pipelines/nf-core/demo/subworkflows
├── local
│   └── utils_nfcore_demo_pipeline
│       └── main.nf
└── nf-core
    ├── utils_nextflow_pipeline
    │   ├── main.nf
    │   ├── meta.yml
    │   └── tests
    ├── utils_nfcore_pipeline
    │   ├── main.nf
    │   ├── meta.yml
    │   └── tests
    └── utils_nfschema_plugin
        ├── main.nf
        ├── meta.yml
        └── tests

9 directories, 7 files

Conforme mencionado acima, o pipeline nf-core/demo não inclui nenhum subfluxo de trabalho específico de análise, então todos os subfluxos de trabalho que vemos aqui são os chamados fluxos de trabalho de 'manutenção' ou 'utilitários', como indicado pelo prefixo utils_ em seus nomes. Esses subfluxos de trabalho são o que produz o cabeçalho nf-core elegante na saída do console, entre outras funções acessórias.

Dica

Além do padrão de nomenclatura, outra indicação de que esses subfluxos de trabalho não realizam nenhuma função verdadeiramente relacionada à análise é que eles não chamam nenhum processo.

Isso conclui a revisão dos componentes principais de código que constituem o pipeline nf-core/demo.

Conclusão

Você agora tem uma compreensão de alto nível da estrutura modular dos pipelines nf-core.

O que vem a seguir?

Criar uma estrutura de pipeline usando ferramentas nf-core.


2. Criar um novo projeto de pipeline

Como você viu, os pipelines nf-core seguem uma estrutura padronizada com muitos arquivos acessórios. Criar tudo isso do zero seria muito tedioso, então a comunidade nf-core desenvolveu ferramentas para fazer isso a partir de um template, para inicializar o processo.

2.1. Executar a ferramenta de criação de pipeline baseada em template

Vamos começar criando um novo pipeline com o comando nf-core pipelines create. Isso criará uma nova estrutura de pipeline usando o template base nf-core, customizado com um nome de pipeline, descrição e autor.

nf-core pipelines create

Executar este comando abrirá uma Interface de Usuário de Texto (TUI) para criação de pipeline:

Esta TUI pedirá que você forneça informações básicas sobre seu pipeline e oferecerá uma escolha de recursos para incluir ou excluir na estrutura do seu pipeline.

  • Na tela de boas-vindas, clique em Let's go!.
  • Na tela Choose pipeline type, clique em Custom.
  • Insira os detalhes do seu pipeline como a seguir (substituindo < SEU NOME > pelo seu próprio nome), então clique em Next.
[ ] GitHub organisation: core
[ ] Workflow name: hello
[ ] A short description of your pipeline: A basic nf-core style version of Hello Nextflow
[ ] Name of the main author(s): < YOUR NAME >
  • Na tela Template features, defina Toggle all features como off, então habilite seletivamente os seguintes. Verifique suas seleções e clique em Continue.
[ ] Add testing profiles
[ ] Use nf-core components
[ ] Use nf-schema
[ ] Add configuration files
[ ] Add documentation
  • Na tela Final details, clique em Finish. Aguarde o pipeline ser criado, então clique em Continue.
  • Na tela Create GitHub repository, clique em Finish without creating a repo. Isso exibirá instruções para criar um repositório GitHub posteriormente. Ignore-as e clique em Close.

Quando a TUI fechar, você deverá ver a seguinte saída no console.

Saída do comando
                                          ,--./,-.
          ___     __   __   __   ___     /,-._.--~\
    |\ | |__  __ /  ` /  \ |__) |__         }  {
    | \| |       \__, \__/ |  \ |___     \`-._,-`-,
                                          `._,._,'

    nf-core/tools version 3.5.2 - https://nf-co.re


INFO     Launching interactive nf-core pipeline creation tool.

Não há confirmação explícita na saída do console de que a criação do pipeline funcionou, mas você deverá ver um novo diretório chamado core-hello.

Visualize o conteúdo do novo diretório para ver quanto trabalho você economizou usando o template.

tree core-hello
Conteúdo do diretório
core-hello/
├── README.md
├── assets
│   ├── samplesheet.csv
│   └── schema_input.json
├── conf
│   ├── base.config
│   ├── modules.config
│   ├── test.config
│   └── test_full.config
├── docs
│   ├── README.md
│   ├── output.md
│   └── usage.md
├── main.nf
├── modules.json
├── nextflow.config
├── nextflow_schema.json
├── subworkflows
│   ├── local
│   │   └── utils_nfcore_hello_pipeline
│   │       └── main.nf
│   └── nf-core
│       ├── utils_nextflow_pipeline
│       │   ├── main.nf
│       │   ├── meta.yml
│       │   └── tests
│       │       ├── main.function.nf.test
│       │       ├── main.function.nf.test.snap
│       │       ├── main.workflow.nf.test
│       │       └── nextflow.config
│       ├── utils_nfcore_pipeline
│       │   ├── main.nf
│       │   ├── meta.yml
│       │   └── tests
│       │       ├── main.function.nf.test
│       │       ├── main.function.nf.test.snap
│       │       ├── main.workflow.nf.test
│       │       ├── main.workflow.nf.test.snap
│       │       └── nextflow.config
│       └── utils_nfschema_plugin
│           ├── main.nf
│           ├── meta.yml
│           └── tests
│               ├── main.nf.test
│               ├── nextflow.config
│               └── nextflow_schema.json
└── workflows
    └── hello.nf

15 directories, 34 files

São muitos arquivos! Não se preocupe se ainda estiver se sentindo um pouco perdido; vamos percorrer as partes importantes em breve, e então passo a passo ao longo do restante do curso.

No geral, isso deve ser semelhante à estrutura de código que observamos para o pipeline nf-core/demo, exceto que não há diretório modules aqui.

2.2. Testar que a estrutura é funcional

Acredite ou não, mesmo que você ainda não tenha adicionado nenhum módulo para fazer trabalho real, a estrutura do pipeline pode realmente ser executada usando o perfil de teste, da mesma forma que executamos o pipeline nf-core/demo.

nextflow run ./core-hello -profile docker,test --outdir core-hello-results
Saída do comando
N E X T F L O W   ~  version 25.10.4

Launching `./core-hello/main.nf` [scruffy_marconi] DSL2 - revision: b9e9b3b8de

Downloading plugin nf-schema@2.5.1
Input/output options
  input                     : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv
  outdir                    : core-hello-results

Institutional config options
  config_profile_name       : Test profile
  config_profile_description: Minimal test dataset to check pipeline function

Generic options
  trace_report_suffix       : 2025-11-21_04-47-18

Core Nextflow options
  runName                   : scruffy_marconi
  containerEngine           : docker
  launchDir                 : /workspaces/training/hello-nf-core
  workDir                   : /workspaces/training/hello-nf-core/work
  projectDir                : /workspaces/training/hello-nf-core/core-hello
  userName                  : root
  profile                   : docker,test
  configFiles               : /workspaces/training/hello-nf-core/core-hello/nextflow.config

!! Only displaying parameters that differ from the pipeline defaults !!
------------------------------------------------------
-[core/hello] Pipeline completed successfully-

Isso mostra que toda a configuração básica está no lugar. Então onde estão as saídas? Existem algumas?

Na verdade, um novo diretório de resultados chamado core-hello-results foi criado contendo os relatórios de execução padrão:

tree core-hello-results
Conteúdo do diretório
core-hello-results
└── pipeline_info
    ├── execution_report_2025-11-21_04-47-18.html
    ├── execution_timeline_2025-11-21_04-47-18.html
    ├── execution_trace_2025-11-21_04-47-18.txt
    ├── hello_software_versions.yml
    ├── params_2025-11-21_04-47-18.json
    └── pipeline_dag_2025-11-21_04-47-18.html

1 directory, 6 files

Você pode dar uma olhada nos relatórios para ver o que foi executado, e a resposta é: nada!

relatório de timeline de execução vazio

Vamos dar uma olhada mais de perto no que realmente está dentro da caixa.

2.3. Examinar a estrutura do scaffold

Se você se lembrar da estrutura do pipeline nf-core/demo, havia um arquivo main.nf contendo um fluxo de trabalho entrypoint que encapsulava o fluxo de trabalho DEMO. Agora, se você abrir o arquivo main.nf no seu projeto recém-criado, verá que ele importa um fluxo de trabalho chamado HELLO de workflows/hello.nf. Esse é o equivalente direto ao fluxo de trabalho DEMO, embora no momento seja apenas um placeholder.

E de acordo com isso, esta é a estrutura geral do scaffold do pipeline:

subworkflows/workflows/hello.nfmain.nfincludelocal/utils_nfcore_demo_pipeline/main.nfnf-core/utils_*/main.nf

Isso deve lembrá-lo da estrutura do pipeline nf-core/demo! A única diferença real é que o fluxo de trabalho DEMO incluía processos de módulos. Aqui, o fluxo de trabalho HELLO equivalente ainda não inclui nenhum processo.

Vamos dar uma olhada mais de perto.

2.4. Examinar o fluxo de trabalho placeholder

Este serve como o placeholder para nosso fluxo de trabalho de análise, com alguma funcionalidade nf-core já implementada.

core-hello/workflows/hello.nf
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
include { paramsSummaryMap       } from 'plugin/nf-schema'
include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline'

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    RUN MAIN WORKFLOW
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

workflow HELLO {

    take:
    ch_samplesheet // channel: samplesheet read in from --input
    main:

    ch_versions = channel.empty()

    //
    // Agrupar e salvar versões de software
    //
    def topic_versions = Channel.topic("versions")
        .distinct()
        .branch { entry ->
            versions_file: entry instanceof Path
            versions_tuple: true
        }

    def topic_versions_string = topic_versions.versions_tuple
        .map { process, tool, version ->
            [ process[process.lastIndexOf(':')+1..-1], "  ${tool}: ${version}" ]
        }
        .groupTuple(by:0)
        .map { process, tool_versions ->
            tool_versions.unique().sort()
            "${process}:\n${tool_versions.join('\n')}"
        }

    softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file))
        .mix(topic_versions_string)
        .collectFile(
            storeDir: "${params.outdir}/pipeline_info",
            name:  'hello_software_'  + 'versions.yml',
            sort: true,
            newLine: true
        ).set { ch_collated_versions }


    emit:
    versions       = ch_versions                 // channel: [ path(versions.yml) ]

}

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    THE END
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

Comparado a um fluxo de trabalho Nextflow básico como o desenvolvido em Hello Nextflow, você notará algumas coisas novas aqui (linhas destacadas acima):

  • O bloco workflow tem um nome
  • As entradas do fluxo de trabalho são declaradas usando a palavra-chave take: e a construção do canal é movida para o fluxo de trabalho pai
  • O conteúdo do fluxo de trabalho é colocado dentro de um bloco main:
  • As saídas são declaradas usando a palavra-chave emit:

Esses são recursos opcionais do Nextflow que tornam o fluxo de trabalho componível, o que significa que ele pode ser chamado de dentro de outro fluxo de trabalho.

O bloco Channel.topic

Você pode ter notado o bloco def topic_versions = Channel.topic("versions") começando na linha 17. Este é um código de manutenção padrão que coleta informações de versão de software de todos os módulos automaticamente. O nf-core está implementando esse mecanismo em todos os pipelines em 2026, então você o verá em todos os novos pipelines daqui para frente. A Parte 4 deste curso explica como ele funciona em detalhes.

Vamos precisar conectar a lógica relevante do nosso fluxo de trabalho de interesse nessa estrutura.

Conclusão

Você agora sabe como criar uma estrutura de pipeline usando ferramentas nf-core e compará-la com a estrutura do pipeline demo.

O que vem a seguir?

Aprenda como tornar um fluxo de trabalho simples componível como prelúdio para torná-lo compatível com nf-core.


3. Tornar o fluxo de trabalho Hello Nextflow componível

Agora é hora de trabalhar na integração do nosso fluxo de trabalho na estrutura nf-core.

Como lembrete, estamos trabalhando com o fluxo de trabalho apresentado no nosso curso de treinamento Hello Nextflow. Esse fluxo de trabalho foi escrito como um fluxo de trabalho simples sem nome que pode ser executado sozinho.

Para mapear claramente quais partes do fluxo de trabalho original devem ir para onde na estrutura nf-core, vamos começar transformando o fluxo de trabalho Hello original em um fluxo de trabalho componível que pode ser executado de dentro de um fluxo de trabalho pai, como o template nf-core requer.

Isto é o que estamos tentando construir agora:

hello.nfsayHello.nfconvertToUpper.nfcollectGreetings.nfcowpy.nfmain.nfincludeincludemodules/

Efetivamente, queremos imitar a estrutura modular do scaffold nf-core, mas com menos complexidade para começar.

Fornecemos uma cópia limpa e totalmente funcional do fluxo de trabalho Hello Nextflow completo no diretório original-hello junto com seus módulos e o arquivo CSV padrão que ele espera usar como entrada.

tree original-hello/
Conteúdo do diretório
original-hello/
├── hello.nf
├── modules
│   ├── collectGreetings.nf
│   ├── convertToUpper.nf
│   ├── cowpy.nf
│   └── sayHello.nf
└── nextflow.config

1 directory, 6 files

Sinta-se à vontade para executá-lo para se convencer de que funciona:

nextflow run original-hello/hello.nf
Saída do comando
N E X T F L O W   ~  version 25.10.4

Launching `original-hello/hello.nf` [goofy_babbage] DSL2 - revision: e9e72441e9

executor >  local (8)
[a4/081cec] sayHello (1)       | 3 of 3 ✔
[e7/7e9058] convertToUpper (3) | 3 of 3 ✔
[0c/17263b] collectGreetings   | 1 of 1 ✔
[94/542280] cowpy              | 1 of 1 ✔

Se funcionou para você, está pronto para começar a modificar.

3.1. Modificar o fluxo de trabalho Hello original

Vamos abrir o arquivo de fluxo de trabalho hello.nf para inspecionar o código, que é mostrado completo abaixo (sem contar os processos, que estão em módulos):

original-hello/hello.nf
#!/usr/bin/env nextflow

/*
* Pipeline parameters
*/
params.greeting = 'greetings.csv'
params.batch = 'test-batch'
params.character = 'turkey'

// Inclui módulos
include { sayHello } from './modules/sayHello.nf'
include { convertToUpper } from './modules/convertToUpper.nf'
include { collectGreetings } from './modules/collectGreetings.nf'
include { cowpy } from './modules/cowpy.nf'

workflow {

  // cria um canal para entradas de um arquivo CSV
  greeting_ch = channel.fromPath(params.greeting)
                      .splitCsv()
                      .map { line -> line[0] }

  // emite uma saudação
  sayHello(greeting_ch)

  // converte a saudação para maiúsculas
  convertToUpper(sayHello.out)

  // coleta todas as saudações em um arquivo
  collectGreetings(convertToUpper.out.collect(), params.batch)

  // gera arte ASCII das saudações com cowpy
  cowpy(collectGreetings.out.outfile, params.character)
}

Como você pode ver, este fluxo de trabalho foi escrito como um fluxo de trabalho simples sem nome que pode ser executado sozinho. Para torná-lo componível, vamos fazer as seguintes mudanças:

  1. Nomear o fluxo de trabalho
  2. Substituir a construção do canal por take:
  3. Prefaciar as operações do fluxo de trabalho com main:
  4. Adicionar declaração emit:

Vamos percorrer as mudanças necessárias uma a uma.

3.1.1. Nomear o fluxo de trabalho

Primeiro, vamos dar um nome ao fluxo de trabalho para que possamos nos referir a ele de um fluxo de trabalho pai.

original-hello/hello.nf
workflow HELLO {
original-hello/hello.nf
workflow {

As mesmas convenções se aplicam aos nomes de fluxos de trabalho e aos nomes de módulos.

3.1.2. Substituir construção de canal por take

Agora, substitua a construção do canal por uma simples declaração take declarando as entradas esperadas.

original-hello/hello.nf
    take:
    // canal de saudações
    greeting_ch
original-hello/hello.nf
    // cria um canal para entradas de um arquivo CSV
    greeting_ch = channel.fromPath(params.greeting)
                        .splitCsv()
                        .map { line -> line[0] }

Isso deixa os detalhes de como as entradas são fornecidas para o fluxo de trabalho pai.

Já que estamos nisso, também podemos comentar a linha params.greeting = 'greetings.csv'

original-hello/hello.nf
3
4
5
6
7
8
    /*
    * Pipeline parameters
    */
    //params.greeting = 'greetings.csv'
    params.batch = 'test-batch'
    params.character = 'turkey'
original-hello/hello.nf
3
4
5
6
7
8
    /*
    * Pipeline parameters
    */
    params.greeting = 'greetings.csv'
    params.batch = 'test-batch'
    params.character = 'turkey'

Nota

Se você tiver a extensão do servidor de linguagem Nextflow instalada, o verificador de sintaxe iluminará seu código com rabiscos vermelhos. Isso ocorre porque se você colocar uma declaração take:, também precisa ter um main:.

Adicionaremos isso no próximo passo.

3.1.3. Prefaciar operações do fluxo de trabalho com declaração main

Em seguida, adicione uma declaração main antes das demais operações chamadas no corpo do fluxo de trabalho.

original-hello/hello.nf
    main:

    // emite uma saudação
    sayHello(greeting_ch)

    // converte a saudação para maiúsculas
    convertToUpper(sayHello.out)

    // coleta todas as saudações em um arquivo
    collectGreetings(convertToUpper.out.collect(), params.batch)

    // gera arte ASCII das saudações com cowpy
    cowpy(collectGreetings.out.outfile, params.character)
original-hello/hello.nf
    // emite uma saudação
    sayHello(greeting_ch)

    // converte a saudação para maiúsculas
    convertToUpper(sayHello.out)

    // coleta todas as saudações em um arquivo
    collectGreetings(convertToUpper.out.collect(), params.batch)

    // gera arte ASCII das saudações com cowpy
    cowpy(collectGreetings.out.outfile, params.character)

Isso basicamente diz 'isto é o que este fluxo de trabalho faz'.

3.1.4. Adicionar declaração emit

Finalmente, adicione uma declaração emit declarando quais são as saídas finais do fluxo de trabalho.

original-hello/hello.nf
    emit:
    cowpy_hellos = cowpy.out

Esta é uma adição completamente nova ao código em comparação com o fluxo de trabalho original.

3.1.5. Recapitulação das mudanças concluídas

Se você fez todas as mudanças conforme descrito, seu fluxo de trabalho deve agora se parecer com isto:

original-hello/hello.nf
#!/usr/bin/env nextflow

/*
* Pipeline parameters
*/
// params.greeting = 'greetings.csv'
params.batch = 'test-batch'
params.character = 'turkey'

// Inclui módulos
include { sayHello } from './modules/sayHello.nf'
include { convertToUpper } from './modules/convertToUpper.nf'
include { collectGreetings } from './modules/collectGreetings.nf'
include { cowpy } from './modules/cowpy.nf'

workflow HELLO {

    take:
    // canal de saudações
    greeting_ch

    main:

    // emite uma saudação
    sayHello(greeting_ch)

    // converte a saudação para maiúsculas
    convertToUpper(sayHello.out)

    // coleta todas as saudações em um arquivo
    collectGreetings(convertToUpper.out.collect(), params.batch)

    // gera arte ASCII das saudações com cowpy
    cowpy(collectGreetings.out.outfile, params.character)

    emit:
    cowpy_hellos = cowpy.out
}

Isso descreve tudo que o Nextflow precisa EXCETO o que alimentar no canal de entrada. Isso será definido no fluxo de trabalho pai, também chamado de fluxo de trabalho entrypoint.

3.2. Fazer um fluxo de trabalho entrypoint de teste

Antes de integrar nosso fluxo de trabalho componível na estrutura nf-core complexa, vamos verificar se funciona corretamente. Podemos fazer um fluxo de trabalho entrypoint simples para testar o fluxo de trabalho componível isoladamente.

Crie um arquivo em branco chamado main.nf no mesmo diretório original-hello.

touch original-hello/main.nf

Copie o seguinte código para o arquivo main.nf.

original-hello/main.nf
#!/usr/bin/env nextflow

// importar o código do fluxo de trabalho do arquivo hello.nf
include { HELLO } from './hello.nf'

// declarar parâmetro de entrada
params.greeting = 'greetings.csv'

workflow {
  // criar um canal para entradas de um arquivo CSV
  greeting_ch = channel.fromPath(params.greeting)
                      .splitCsv()
                      .map { line -> line[0] }

  // chamar o fluxo de trabalho importado no canal de saudações
  HELLO(greeting_ch)

  // visualizar as saídas emitidas pelo fluxo de trabalho
  HELLO.out.view { output -> "Output: $output" }
}

Há duas observações importantes a fazer aqui:

  • A sintaxe para chamar o fluxo de trabalho importado é essencialmente a mesma que a sintaxe para chamar módulos.
  • Tudo que está relacionado a trazer as entradas para o fluxo de trabalho (parâmetro de entrada e construção de canal) agora é declarado neste fluxo de trabalho pai.

Nota

Nomear o arquivo de fluxo de trabalho entrypoint main.nf é uma convenção, não um requisito.

Se você seguir esta convenção, pode omitir a especificação do nome do arquivo de fluxo de trabalho no seu comando nextflow run. O Nextflow procurará automaticamente por um arquivo chamado main.nf no diretório de execução.

No entanto, você pode nomear o arquivo de fluxo de trabalho entrypoint de outra forma se preferir. Nesse caso, certifique-se de especificar o nome do arquivo de fluxo de trabalho no seu comando nextflow run.

3.3. Testar que o fluxo de trabalho executa

Finalmente temos todas as peças que precisamos para verificar que o fluxo de trabalho componível funciona. Vamos executá-lo!

nextflow run ./original-hello

Aqui você vê a vantagem de usar a convenção de nomenclatura main.nf. Se tivéssemos nomeado o fluxo de trabalho entrypoint algo_diferente.nf, teríamos que fazer nextflow run original-hello/algo_diferente.nf.

Se você fez todas as mudanças corretamente, isso deve executar até a conclusão.

Saída do comando
N E X T F L O W   ~  version 25.10.4

Launching `original-hello/main.nf` [friendly_wright] DSL2 - revision: 1ecd2d9c0a

executor >  local (8)
[24/c6c0d8] HELLO:sayHello (3)       | 3 of 3 ✔
[dc/721042] HELLO:convertToUpper (3) | 3 of 3 ✔
[48/5ab2df] HELLO:collectGreetings   | 1 of 1 ✔
[e3/693b7e] HELLO:cowpy              | 1 of 1 ✔
Output: /workspaces/training/hello-nf-core/work/e3/693b7e48dc119d0c54543e0634c2e7/cowpy-COLLECTED-test-batch-output.txt

Isso significa que atualizamos com sucesso nosso fluxo de trabalho HELLO para ser componível.

Conclusão

Você sabe como tornar um fluxo de trabalho componível dando-lhe um nome e adicionando declarações take, main e emit, e como chamá-lo de um fluxo de trabalho entrypoint.

O que vem a seguir?

Aprenda como enxertar um fluxo de trabalho componível básico na estrutura nf-core.


4. Ajustar a lógica do fluxo de trabalho atualizado no fluxo de trabalho placeholder

Agora que verificamos que nosso fluxo de trabalho componível funciona corretamente, vamos retornar à estrutura do pipeline nf-core que criamos na seção 1. Queremos integrar o fluxo de trabalho componível que acabamos de desenvolver na estrutura do template nf-core, então o resultado final deve se parecer com isto.

subworkflows/workflows/hello.nfmain.nfincludeincludemodules/local/local/utils_nfcore_demo_pipeline/main.nfnf-core/utils_*/main.nfsayHello.nfconvertToUpper.nfcollectGreetings.nfcowpy.nf

Então como fazemos isso acontecer? Vamos dar uma olhada no conteúdo atual do fluxo de trabalho HELLO em core-hello/workflows/hello.nf (a estrutura nf-core).

core-hello/workflows/hello.nf
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
include { paramsSummaryMap       } from 'plugin/nf-schema'
include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline'

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    RUN MAIN WORKFLOW
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

workflow HELLO {

    take:
    ch_samplesheet // channel: samplesheet read in from --input
    main:

    ch_versions = channel.empty()

    //
    // Agrupar e salvar versões de software
    //
    def topic_versions = Channel.topic("versions")
        .distinct()
        .branch { entry ->
            versions_file: entry instanceof Path
            versions_tuple: true
        }

    def topic_versions_string = topic_versions.versions_tuple
        .map { process, tool, version ->
            [ process[process.lastIndexOf(':')+1..-1], "  ${tool}: ${version}" ]
        }
        .groupTuple(by:0)
        .map { process, tool_versions ->
            tool_versions.unique().sort()
            "${process}:\n${tool_versions.join('\n')}"
        }

    softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file))
        .mix(topic_versions_string)
        .collectFile(
            storeDir: "${params.outdir}/pipeline_info",
            name:  'hello_software_'  + 'versions.yml',
            sort: true,
            newLine: true
        ).set { ch_collated_versions }


    emit:
    versions       = ch_versions                 // channel: [ path(versions.yml) ]

}

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    THE END
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

As linhas destacadas definem a estrutura do fluxo de trabalho componível: workflow HELLO {, take:, main: e emit:. O grande bloco entre as linhas 17–34 é mais substancial: ele lida com a captura de versões de software usando topic channels, um mecanismo que o nf-core está implementando em todos os pipelines em 2026. Explicaremos isso na Parte 4; por enquanto, trate-o como código padrão que você pode deixar sem alterações.

Precisamos adicionar o código relevante da versão componível do fluxo de trabalho original que desenvolvemos na seção 2.

Vamos abordar isso nos seguintes estágios:

  1. Copiar os módulos e configurar importações de módulos
  2. Deixar a declaração take como está
  3. Adicionar a lógica do fluxo de trabalho ao bloco main
  4. Atualizar o bloco emit

Nota

Vamos ignorar o bloco de captura de versão nesta primeira passagem. A Parte 4 explica como ele funciona.

4.1. Copiar os módulos e configurar importações de módulos

Os quatro processos do nosso fluxo de trabalho Hello Nextflow são armazenados como módulos em original-hello/modules/. Precisamos copiar esses módulos para a estrutura do projeto nf-core (em core-hello/modules/local/) e adicionar declarações de importação ao arquivo de fluxo de trabalho nf-core.

Primeiro vamos copiar os arquivos de módulo de original-hello/ para core-hello/:

mkdir -p core-hello/modules/local/
cp original-hello/modules/* core-hello/modules/local/.

Você deve agora ver o diretório de módulos listado em core-hello/.

tree core-hello/modules
Conteúdo do diretório
core-hello/modules
└── local
    ├── collectGreetings.nf
    ├── convertToUpper.nf
    ├── cowpy.nf
    └── sayHello.nf

1 directory, 4 files

Agora vamos configurar as declarações de importação de módulos.

Estas eram as declarações de importação no fluxo de trabalho original-hello/hello.nf:

original-hello/hello.nf
// Inclui módulos
include { sayHello } from './modules/sayHello.nf'
include { convertToUpper } from './modules/convertToUpper.nf'
include { collectGreetings } from './modules/collectGreetings.nf'
include { cowpy } from './modules/cowpy.nf'

Abra o arquivo core-hello/workflows/hello.nf e transponha essas declarações de importação para ele conforme mostrado abaixo.

core-hello/workflows/hello.nf
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
include { paramsSummaryMap       } from 'plugin/nf-schema'
include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline'
include { sayHello               } from '../modules/local/sayHello.nf'
include { convertToUpper         } from '../modules/local/convertToUpper.nf'
include { collectGreetings       } from '../modules/local/collectGreetings.nf'
include { cowpy                  } from '../modules/local/cowpy.nf'
core-hello/workflows/hello.nf
1
2
3
4
5
6
7
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
include { paramsSummaryMap       } from 'plugin/nf-schema'
include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline'

Mais duas observações interessantes aqui:

  • Adaptamos a formatação das declarações de importação para seguir a convenção de estilo nf-core.
  • Atualizamos os caminhos relativos para os módulos para refletir que eles agora estão armazenados em um nível diferente de aninhamento.

4.2. Deixar a declaração take como está

O projeto nf-core tem muita funcionalidade pré-construída em torno do conceito de samplesheet, que é tipicamente um arquivo CSV contendo dados em colunas. Como isso é essencialmente o que nosso arquivo greetings.csv é, vamos manter a declaração take atual como está, e simplesmente atualizar o nome do canal de entrada no próximo passo.

core-hello/workflows/hello.nf
    take:
    ch_samplesheet // channel: samplesheet read in from --input

O tratamento de entrada será feito antes deste fluxo de trabalho (não neste arquivo de código).

4.3. Adicionar a lógica do fluxo de trabalho ao bloco main

Agora que nossos módulos estão disponíveis para o fluxo de trabalho, podemos conectar a lógica do fluxo de trabalho no bloco main.

Como lembrete, este é o código relevante no fluxo de trabalho original, que não mudou muito quando o tornamos componível (apenas adicionamos a linha main:):

original-hello/hello.nf
    main:

    // emite uma saudação
    sayHello(greeting_ch)

    // converte a saudação para maiúsculas
    convertToUpper(sayHello.out)

    // coleta todas as saudações em um arquivo
    collectGreetings(convertToUpper.out.collect(), params.batch)

    // gera arte ASCII das saudações com cowpy
    cowpy(collectGreetings.out.outfile, params.character)

Precisamos copiar o código que vem depois de main: para a nova versão do fluxo de trabalho.

Já existe algum código lá que tem a ver com capturar as versões das ferramentas que são executadas pelo fluxo de trabalho. Vamos deixar isso em paz por enquanto (lidaremos com as versões de ferramentas mais tarde). Manteremos a inicialização ch_versions = channel.empty() no topo, depois inseriremos nossa lógica de fluxo de trabalho, mantendo o código de coleta de versões no final. Esta ordenação faz sentido porque em um pipeline real, os processos emitiriam informações de versão que seriam adicionadas ao canal ch_versions conforme o fluxo de trabalho executa.

core-hello/workflows/hello.nf
workflow HELLO {

    take:
    ch_samplesheet // channel: samplesheet read in from --input

    main:

    ch_versions = channel.empty()

    // emitir uma saudação
    sayHello(greeting_ch)

    // converter a saudação para maiúsculas
    convertToUpper(sayHello.out)

    // coletar todas as saudações em um arquivo
    collectGreetings(convertToUpper.out.collect(), params.batch)

    // gerar arte ASCII das saudações com cowpy
    cowpy(collectGreetings.out.outfile, params.character)

    //
    // Agrupar e salvar versões de software
    //
    def topic_versions = Channel.topic("versions")
        .distinct()
        .branch { entry ->
            versions_file: entry instanceof Path
            versions_tuple: true
        }

    def topic_versions_string = topic_versions.versions_tuple
        .map { process, tool, version ->
            [ process[process.lastIndexOf(':')+1..-1], "  ${tool}: ${version}" ]
        }
        .groupTuple(by:0)
        .map { process, tool_versions ->
            tool_versions.unique().sort()
            "${process}:\n${tool_versions.join('\n')}"
        }

    softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file))
        .mix(topic_versions_string)
        .collectFile(
            storeDir: "${params.outdir}/pipeline_info",
            name:  'hello_software_'  + 'versions.yml',
            sort: true,
            newLine: true
        ).set { ch_collated_versions }


    emit:
    versions       = ch_versions                 // channel: [ path(versions.yml) ]

}
core-hello/workflows/hello.nf
workflow HELLO {

    take:
    ch_samplesheet // channel: samplesheet read in from --input
    main:

    ch_versions = channel.empty()

    //
    // Agrupar e salvar versões de software
    //
    def topic_versions = Channel.topic("versions")
        .distinct()
        .branch { entry ->
            versions_file: entry instanceof Path
            versions_tuple: true
        }

    def topic_versions_string = topic_versions.versions_tuple
        .map { process, tool, version ->
            [ process[process.lastIndexOf(':')+1..-1], "  ${tool}: ${version}" ]
        }
        .groupTuple(by:0)
        .map { process, tool_versions ->
            tool_versions.unique().sort()
            "${process}:\n${tool_versions.join('\n')}"
        }

    softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file))
        .mix(topic_versions_string)
        .collectFile(
            storeDir: "${params.outdir}/pipeline_info",
            name:  'hello_software_'  + 'versions.yml',
            sort: true,
            newLine: true
        ).set { ch_collated_versions }


    emit:
    versions       = ch_versions                 // channel: [ path(versions.yml) ]

}

Você notará que também adicionamos uma linha em branco antes de main: para tornar o código mais legível.

Isso parece ótimo, mas ainda precisamos atualizar o nome do canal que estamos passando para o processo sayHello() de greeting_ch para ch_samplesheet conforme mostrado abaixo, para corresponder ao que está escrito sob a palavra-chave take:.

core-hello/workflows/hello.nf
    // emitir uma saudação (atualizado para usar a convenção nf-core para samplesheets)
    sayHello(ch_samplesheet)
core-hello/workflows/hello.nf
    // emite uma saudação
    sayHello(greeting_ch)

Agora a lógica do fluxo de trabalho está corretamente conectada.

4.4. Atualizar o bloco emit

Finalmente, precisamos atualizar o bloco emit para incluir a declaração das saídas finais do fluxo de trabalho.

core-hello/workflows/hello.nf
    emit:
    cowpy_hellos   = cowpy.out
    versions       = ch_versions                 // channel: [ path(versions.yml) ]
core-hello/workflows/hello.nf
    emit:
    versions       = ch_versions                 // channel: [ path(versions.yml) ]

Isso conclui as modificações que precisamos fazer no próprio fluxo de trabalho HELLO. Neste ponto, alcançamos a estrutura geral de código que nos propusemos a implementar.

Conclusão

Você sabe como ajustar as peças principais de um fluxo de trabalho componível em um fluxo de trabalho placeholder nf-core.

O que vem a seguir?

Aprenda como adaptar o tratamento de entradas na estrutura do pipeline nf-core.


5. Adaptar o tratamento de entrada

Agora que integramos com sucesso nossa lógica de fluxo de trabalho na estrutura nf-core, precisamos abordar mais uma peça crítica: garantir que nossos dados de entrada sejam processados corretamente. O template nf-core vem com tratamento de entrada sofisticado projetado para conjuntos de dados de genômica complexos, então precisamos adaptá-lo para funcionar com nosso arquivo greetings.csv mais simples.

5.1. Identificar onde as entradas são tratadas

O primeiro passo é descobrir onde o tratamento de entrada é feito.

Você pode se lembrar que quando reescrevemos o fluxo de trabalho Hello Nextflow para ser componível, movemos a declaração de parâmetro de entrada um nível acima, no fluxo de trabalho entrypoint main.nf. Então vamos dar uma olhada no fluxo de trabalho entrypoint main.nf de nível superior que foi criado como parte da estrutura do pipeline:

core-hello/main.nf
#!/usr/bin/env nextflow
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    core/hello
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Github : https://github.com/core/hello
----------------------------------------------------------------------------------------
*/

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS / WORKFLOWS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

include { HELLO  } from './workflows/hello'
include { PIPELINE_INITIALISATION } from './subworkflows/local/utils_nfcore_hello_pipeline'
include { PIPELINE_COMPLETION     } from './subworkflows/local/utils_nfcore_hello_pipeline'
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    NAMED WORKFLOWS FOR PIPELINE
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

//
// WORKFLOW: Run main analysis pipeline depending on type of input
//
workflow CORE_HELLO {

    take:
    samplesheet // channel: samplesheet read in from --input

    main:

    //
    // WORKFLOW: Run pipeline
    //
    HELLO (
        samplesheet
    )
}
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    RUN MAIN WORKFLOW
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

workflow {

    main:
    //
    // SUBWORKFLOW: Run initialisation tasks
    //
    PIPELINE_INITIALISATION (
        params.version,
        params.validate_params,
        params.monochrome_logs,
        args,
        params.outdir,
        params.input,
        params.help,
        params.help_full,
        params.show_hidden
    )

    //
    // WORKFLOW: Run main workflow
    //
    CORE_HELLO (
        PIPELINE_INITIALISATION.out.samplesheet
    )
    //
    // SUBWORKFLOW: Run completion tasks
    //
    PIPELINE_COMPLETION (
        params.outdir,
        params.monochrome_logs,
    )
}

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    THE END
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

O projeto nf-core faz uso intenso de subfluxos de trabalho aninhados, então esta parte pode ser um pouco confusa na primeira abordagem.

O que importa aqui é que existem dois fluxos de trabalho definidos:

  • CORE_HELLO é um wrapper fino para executar o fluxo de trabalho HELLO que acabamos de terminar de adaptar em core-hello/workflows/hello.nf.
  • Um fluxo de trabalho sem nome que chama CORE_HELLO bem como dois outros subfluxos de trabalho, PIPELINE_INITIALISATION e PIPELINE_COMPLETION.

Aqui está um diagrama de como eles se relacionam:

PIPELINE_INITIALISATIONPIPELINE_COMPLETIONCORE_HELLO { HELLO}samplesheetdata filesoutput filespipeline reportsunnamed workflow in main.nf

Importante, não podemos encontrar nenhum código construindo um canal de entrada neste nível, apenas referências a uma samplesheet fornecida via parâmetro --input.

Um pouco de investigação revela que o tratamento de entrada é feito pelo subfluxo de trabalho PIPELINE_INITIALISATION, apropriadamente, que é importado de core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf.

Se abrirmos esse arquivo e rolarmos para baixo, chegamos a este pedaço de código:

core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf
    //
    // Cria canal a partir do arquivo de entrada fornecido através de params.input
    //

    channel
        .fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json"))
        .map {
            meta, fastq_1, fastq_2 ->
                if (!fastq_2) {
                    return [ meta.id, meta + [ single_end:true ], [ fastq_1 ] ]
                } else {
                    return [ meta.id, meta + [ single_end:false ], [ fastq_1, fastq_2 ] ]
                }
        }
        .groupTuple()
        .map { samplesheet ->
            validateInputSamplesheet(samplesheet)
        }
        .map {
            meta, fastqs ->
                return [ meta, fastqs.flatten() ]
        }
        .set { ch_samplesheet }

    emit:
    samplesheet = ch_samplesheet
    versions    = ch_versions

Esta é a fábrica de canais que analisa a samplesheet e a passa adiante em uma forma que está pronta para ser consumida pelo fluxo de trabalho HELLO.

Nota

A sintaxe acima é um pouco diferente do que usamos anteriormente, mas basicamente isto:

channel.<...>.set { ch_samplesheet }

é equivalente a isto:

ch_samplesheet = channel.<...>

Este código envolve algumas etapas de análise e validação que são altamente específicas para a samplesheet de exemplo incluída no template de pipeline nf-core, que no momento da escrita é muito específica de domínio e não adequada para nosso projeto de pipeline simples.

5.2. Substituir o código de canal de entrada do template

A boa notícia é que as necessidades do nosso pipeline são muito mais simples, então podemos substituir tudo isso pelo código de construção de canal que desenvolvemos no fluxo de trabalho Hello Nextflow original.

Como lembrete, isto é como a construção de canal se parecia (como visto no diretório de soluções):

solutions/composable-hello/main.nf
    // criar um canal para entradas de um arquivo CSV
    greeting_ch = channel.fromPath(params.greeting)
        .splitCsv()
        .map { line -> line[0] }

Então só precisamos conectar isso no fluxo de trabalho de inicialização, com pequenas mudanças: atualizamos o nome do canal de greeting_ch para ch_samplesheet, e o nome do parâmetro de params.greeting para params.input (veja a linha destacada).

core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf
    //
    // Cria canal a partir do arquivo de entrada fornecido através de params.input
    //

    ch_samplesheet = channel.fromPath(params.input)
        .splitCsv()
        .map { line -> line[0] }

    emit:
    samplesheet = ch_samplesheet
    versions    = ch_versions
core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf
    //
    // Cria canal a partir do arquivo de entrada fornecido através de params.input
    //

    channel
        .fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json"))
        .map {
            meta, fastq_1, fastq_2 ->
                if (!fastq_2) {
                    return [ meta.id, meta + [ single_end:true ], [ fastq_1 ] ]
                } else {
                    return [ meta.id, meta + [ single_end:false ], [ fastq_1, fastq_2 ] ]
                }
        }
        .groupTuple()
        .map { samplesheet ->
            validateInputSamplesheet(samplesheet)
        }
        .map {
            meta, fastqs ->
                return [ meta, fastqs.flatten() ]
        }
        .set { ch_samplesheet }

    emit:
    samplesheet = ch_samplesheet
    versions    = ch_versions

Isso completa as mudanças que precisamos fazer para que o processamento de entrada funcione.

Na sua forma atual, isso não nos permitirá aproveitar as capacidades integradas do nf-core para validação de schema, mas podemos adicionar isso mais tarde. Por enquanto, estamos focados em mantê-lo o mais simples possível para chegar a algo que possamos executar com sucesso em dados de teste.

5.3. Atualizar o perfil de teste

Falando em dados de teste e parâmetros, vamos atualizar o perfil de teste para este pipeline para usar a mini-samplesheet greetings.csv em vez da samplesheet de exemplo fornecida no template.

Em core-hello/conf, encontramos dois perfis de teste do template: test.config e test_full.config, que são destinados a testar uma pequena amostra de dados e uma de tamanho completo. Dado o propósito do nosso pipeline, não há realmente sentido em configurar um perfil de teste de tamanho completo, então sinta-se à vontade para ignorar ou excluir test_full.config. Vamos focar em configurar test.config para executar em nosso arquivo greetings.csv com alguns parâmetros padrão.

5.3.1. Copiar o arquivo greetings.csv

Primeiro precisamos copiar o arquivo greetings.csv para um local apropriado em nosso projeto de pipeline. Tipicamente pequenos arquivos de teste são armazenados no diretório assets, então vamos copiar o arquivo do nosso diretório de trabalho.

cp greetings.csv core-hello/assets/.

Agora o arquivo greetings.csv está pronto para ser usado como entrada de teste.

5.3.2. Atualizar o arquivo test.config

Agora podemos atualizar o arquivo test.config da seguinte forma:

core-hello/conf/test.config
params {
    config_profile_name        = 'Test profile'
    config_profile_description = 'Minimal test dataset to check pipeline function'

    // Dados de entrada
    input  = "${projectDir}/assets/greetings.csv"

    // Outros parâmetros
    batch     = 'test'
    character = 'tux'
}
core-hello/conf/test.config
params {
    config_profile_name        = 'Test profile'
    config_profile_description = 'Minimal test dataset to check pipeline function'

    // Dados de entrada
    // TODO nf-core: Specify the paths to your test data on nf-core/test-datasets
    // TODO nf-core: Give any required params for the test so that command line flags are not needed
    input  = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv'
}

Pontos-chave:

  • Usando ${projectDir}: Esta é uma variável implícita do Nextflow que aponta para o diretório onde o script de fluxo de trabalho principal está localizado (a raiz do pipeline). Usá-la garante que o caminho funcione independentemente de onde o pipeline seja executado.
  • Caminhos absolutos: Ao usar ${projectDir}, criamos um caminho absoluto, o que é importante para dados de teste que são enviados com o pipeline.
  • Localização de dados de teste: pipelines nf-core tipicamente armazenam dados de teste no diretório assets/ dentro do repositório do pipeline para pequenos arquivos de teste, ou referenciam conjuntos de dados de teste externos para arquivos maiores.

E já que estamos nisso, vamos apertar os limites de recursos padrão para garantir que isso seja executado em máquinas muito básicas (como as VMs mínimas no GitHub Codespaces):

core-hello/conf/test.config
process {
    resourceLimits = [
        cpus: 2,
        memory: '4.GB',
        time: '1.h'
    ]
}
core-hello/conf/test.config
process {
    resourceLimits = [
        cpus: 4,
        memory: '15.GB',
        time: '1.h'
    ]
}

Isso completa as modificações de código que precisamos fazer.

5.4. Executar o pipeline com o perfil de teste

Isso foi muito, mas finalmente podemos tentar executar o pipeline! Note que temos que adicionar --validate_params false à linha de comando porque ainda não configuramos a validação (isso virá mais tarde).

nextflow run core-hello --outdir core-hello-results -profile test,docker --validate_params false

Se você fez todas as modificações corretamente, deve executar até a conclusão.

Saída do comando
 N E X T F L O W   ~  version 25.10.4

Launching `core-hello/main.nf` [condescending_allen] DSL2 - revision: b9e9b3b8de

Input/output options
  input                     : /workspaces/training/hello-nf-core/core-hello/assets/greetings.csv
  outdir                    : core-hello-results

Institutional config options
  config_profile_name       : Test profile
  config_profile_description: Minimal test dataset to check pipeline function

Generic options
  validate_params           : false
  trace_report_suffix       : 2025-11-21_07-29-37

Core Nextflow options
  runName                   : condescending_allen
  containerEngine           : docker
  launchDir                 : /workspaces/training/hello-nf-core
  workDir                   : /workspaces/training/hello-nf-core/work
  projectDir                : /workspaces/training/hello-nf-core/core-hello
  userName                  : root
  profile                   : test,docker
  configFiles               : /workspaces/training/hello-nf-core/core-hello/nextflow.config

!! Only displaying parameters that differ from the pipeline defaults !!
------------------------------------------------------
executor >  local (1)
[ed/727b7e] CORE_HELLO:HELLO:sayHello (3)       [100%] 3 of 3 ✔
[45/bb6096] CORE_HELLO:HELLO:convertToUpper (3) [100%] 3 of 3 ✔
[81/7e2e34] CORE_HELLO:HELLO:collectGreetings   [100%] 1 of 1 ✔
[96/9442a1] CORE_HELLO:HELLO:cowpy              [100%] 1 of 1 ✔
-[core/hello] Pipeline completed successfully-

Como você pode ver, isso produziu o resumo típico nf-core no início graças ao subfluxo de trabalho de inicialização, e as linhas para cada módulo agora mostram os nomes completos PIPELINE:WORKFLOW:módulo.

5.5. Encontrar as saídas do pipeline

A questão agora é: onde estão as saídas do pipeline? E a resposta é bastante interessante: agora há dois lugares diferentes para procurar os resultados.

Como você pode se lembrar anteriormente, nossa primeira execução do fluxo de trabalho recém-criado produziu um diretório chamado core-hello-results/ que continha vários relatórios de execução e metadados.

tree core-hello-results
Conteúdo do diretório
core-hello-results
└── pipeline_info
    ├── execution_report_2025-11-21_04-47-18.html
    ├── execution_report_2025-11-21_07-29-37.html
    ├── execution_timeline_2025-11-21_04-47-18.html
    ├── execution_timeline_2025-11-21_07-29-37.html
    ├── execution_trace_2025-11-21_04-47-18.txt
    ├── execution_trace_2025-11-21_07-29-37.txt
    ├── hello_software_versions.yml
    ├── params_2025-11-21_04-47-13.json
    ├── params_2025-11-21_07-29-41.json
    ├── pipeline_dag_2025-11-21_04-47-18.html
    └── pipeline_dag_2025-11-21_07-29-37.html

1 directory, 12 files

Você vê que obtivemos outro conjunto de relatórios de execução além dos que obtivemos da primeira execução, quando o fluxo de trabalho ainda era apenas um placeholder. Desta vez você vê todas as tarefas que foram executadas como esperado.

relatório de timeline de execução para o pipeline Hello

Nota

Mais uma vez as tarefas não foram executadas em paralelo porque estamos executando em uma máquina minimalista no GitHub Codespaces. Para ver essas executadas em paralelo, tente aumentar a alocação de CPU do seu codespace e os limites de recursos na configuração de teste.

Isso é ótimo, mas nossos resultados reais do pipeline não estão lá!

Aqui está o que aconteceu: não mudamos nada nos próprios módulos, então as saídas tratadas pelas diretivas publishDir em nível de módulo ainda vão para um diretório results conforme especificado no pipeline original.

tree results
Conteúdo do diretório
results
├── Bonjour-output.txt
├── COLLECTED-test-batch-output.txt
├── COLLECTED-test-output.txt
├── cowpy-COLLECTED-test-batch-output.txt
├── cowpy-COLLECTED-test-output.txt
├── Hello-output.txt
├── Hola-output.txt
├── UPPER-Bonjour-output.txt
├── UPPER-Hello-output.txt
└── UPPER-Hola-output.txt

0 directories, 10 files

Ah, ali estão eles, misturados com as saídas de execuções anteriores do pipeline Hello original.

Se quisermos que eles sejam organizados de forma organizada como as saídas do pipeline demo foram, precisaremos mudar como configuramos as saídas para serem publicadas. Mostraremos como fazer isso mais tarde neste curso de treinamento.

E aí está! Pode parecer muito trabalho para obter o mesmo resultado que o pipeline original, mas você obtém todos aqueles relatórios lindos gerados automaticamente, e agora tem uma base sólida para aproveitar recursos adicionais do nf-core, incluindo validação de entrada e alguns recursos legais de tratamento de metadados que cobriremos em uma seção posterior.


Conclusão

Você sabe como converter um pipeline Nextflow regular em um pipeline no estilo nf-core usando o template nf-core. Como parte disso, você aprendeu como tornar um fluxo de trabalho componível, e como identificar os elementos do template nf-core que mais comumente precisam ser adaptados ao desenvolver um pipeline customizado no estilo nf-core.

O que vem a seguir?

Faça uma pausa, isso foi trabalho duro! Quando estiver pronto, prossiga para Parte 3: Usar um módulo nf-core para aprender como aproveitar módulos mantidos pela comunidade do repositório nf-core/modules.