From 95c78a848a6b28d1784c098bd77babfc3dd67422 Mon Sep 17 00:00:00 2001 From: nf-core-bot Date: Tue, 14 Dec 2021 16:42:41 +0000 Subject: [PATCH] Template update for nf-core/tools version 2.2 --- .gitattributes | 2 + .github/CONTRIBUTING.md | 38 ++------ .github/ISSUE_TEMPLATE/bug_report.md | 63 ------------- .github/ISSUE_TEMPLATE/bug_report.yml | 52 ++++++++++ .github/ISSUE_TEMPLATE/config.yml | 1 - .github/ISSUE_TEMPLATE/feature_request.md | 32 ------- .github/ISSUE_TEMPLATE/feature_request.yml | 11 +++ .github/workflows/awsfulltest.yml | 8 +- .github/workflows/awstest.yml | 10 +- .github/workflows/ci.yml | 23 +++-- .github/workflows/linting_comment.yml | 1 + CITATIONS.md | 2 +- README.md | 13 ++- assets/multiqc_config.yaml | 2 +- assets/nf-core-cutandrun_logo.png | Bin 18251 -> 0 bytes assets/nf-core-cutandrun_logo_light.png | 9 ++ assets/sendmail_template.txt | 4 +- bin/scrape_software_versions.py | 36 ------- conf/base.config | 3 + conf/modules.config | 55 ++++++----- conf/test.config | 4 +- docs/images/nf-core-cutandrun_logo.png | Bin 34713 -> 0 bytes docs/images/nf-core-cutandrun_logo_dark.png | 11 +++ docs/images/nf-core-cutandrun_logo_light.png | 13 +++ docs/output.md | 2 +- docs/usage.md | 36 ------- lib/NfcoreSchema.groovy | 26 +++-- lib/NfcoreTemplate.groovy | 30 ++---- lib/Utils.groovy | 7 -- lib/WorkflowMain.groovy | 6 +- modules.json | 9 +- modules/local/functions.nf | 68 ------------- modules/local/get_software_versions.nf | 33 ------- modules/local/samplesheet_check.nf | 24 ++--- .../custom/dumpsoftwareversions/main.nf | 21 +++++ .../custom/dumpsoftwareversions/meta.yml | 34 +++++++ .../templates/dumpsoftwareversions.py | 89 ++++++++++++++++++ modules/nf-core/modules/fastqc/functions.nf | 68 ------------- modules/nf-core/modules/fastqc/main.nf | 39 ++++---- modules/nf-core/modules/fastqc/meta.yml | 7 +- modules/nf-core/modules/multiqc/functions.nf | 68 ------------- modules/nf-core/modules/multiqc/main.nf | 31 +++--- modules/nf-core/modules/multiqc/meta.yml | 7 +- nextflow.config | 31 +++--- nextflow_schema.json | 29 ------ subworkflows/local/input_check.nf | 8 +- workflows/cutandrun.nf | 44 +++------ 47 files changed, 441 insertions(+), 669 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml delete mode 100644 assets/nf-core-cutandrun_logo.png create mode 100644 assets/nf-core-cutandrun_logo_light.png delete mode 100755 bin/scrape_software_versions.py delete mode 100644 docs/images/nf-core-cutandrun_logo.png create mode 100644 docs/images/nf-core-cutandrun_logo_dark.png create mode 100644 docs/images/nf-core-cutandrun_logo_light.png delete mode 100644 modules/local/functions.nf delete mode 100644 modules/local/get_software_versions.nf create mode 100644 modules/nf-core/modules/custom/dumpsoftwareversions/main.nf create mode 100644 modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml create mode 100644 modules/nf-core/modules/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py delete mode 100644 modules/nf-core/modules/fastqc/functions.nf delete mode 100644 modules/nf-core/modules/multiqc/functions.nf diff --git a/.gitattributes b/.gitattributes index 7fe55006..050bb120 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,3 @@ *.config linguist-language=nextflow +modules/nf-core/** linguist-generated +subworkflows/nf-core/** linguist-generated diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8054c7bb..8aa23896 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -68,16 +68,13 @@ If you wish to contribute a new step, please use the following coding standards: 1. Define the corresponding input channel into your new process from the expected previous process channel 2. Write the process block (see below). 3. Define the output channel if needed (see below). -4. Add any new flags/options to `nextflow.config` with a default (see below). -5. Add any new flags/options to `nextflow_schema.json` with help text (with `nf-core schema build`). -6. Add any new flags/options to the help message (for integer/text parameters, print to help the corresponding `nextflow.config` parameter). -7. Add sanity checks for all relevant parameters. -8. Add any new software to the `scrape_software_versions.py` script in `bin/` and the version command to the `scrape_software_versions` process in `main.nf`. -9. Do local tests that the new code works properly and as expected. -10. Add a new test command in `.github/workflow/ci.yml`. -11. If applicable add a [MultiQC](https://https://multiqc.info/) module. -12. Update MultiQC config `assets/multiqc_config.yaml` so relevant suffixes, name clean up, General Statistics Table column order, and module figures are in the right order. -13. Optional: Add any descriptions of MultiQC report sections and output files to `docs/output.md`. +4. Add any new parameters to `nextflow.config` with a default (see below). +5. Add any new parameters to `nextflow_schema.json` with help text (via the `nf-core schema build` tool). +6. Add sanity checks and validation for all relevant parameters. +7. Perform local tests to validate that the new code works as expected. +8. If applicable, add a new test command in `.github/workflow/ci.yml`. +9. Update MultiQC config `assets/multiqc_config.yaml` so relevant suffixes, file name clean up and module plots are in the appropriate order. If applicable, add a [MultiQC](https://https://multiqc.info/) module. +10. Add a description of the output files and if relevant any appropriate images from the MultiQC report to `docs/output.md`. ### Default values @@ -102,27 +99,6 @@ Please use the following naming schemes, to make it easy to understand what is g If you are using a new feature from core Nextflow, you may bump the minimum required version of nextflow in the pipeline with: `nf-core bump-version --nextflow . [min-nf-version]` -### Software version reporting - -If you add a new tool to the pipeline, please ensure you add the information of the tool to the `get_software_version` process. - -Add to the script block of the process, something like the following: - -```bash - --version &> v_.txt 2>&1 || true -``` - -or - -```bash - --help | head -n 1 &> v_.txt 2>&1 || true -``` - -You then need to edit the script `bin/scrape_software_versions.py` to: - -1. Add a Python regex for your tool's `--version` output (as in stored in the `v_.txt` file), to ensure the version is reported as a `v` and the version number e.g. `v2.1.1` -2. Add a HTML entry to the `OrderedDict` for formatting in MultiQC. - ### Images and figures For overview images and other documents we follow the nf-core [style guidelines and examples](https://nf-co.re/developers/design_guidelines). diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 566eb1e9..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -name: Bug report -about: Report something that is broken or incorrect -labels: bug ---- - - - -## Check Documentation - -I have checked the following places for your error: - -- [ ] [nf-core website: troubleshooting](https://nf-co.re/usage/troubleshooting) -- [ ] [nf-core/cutandrun pipeline documentation](https://nf-co.re/cutandrun/usage) - -## Description of the bug - - - -## Steps to reproduce - -Steps to reproduce the behaviour: - -1. Command line: -2. See error: - -## Expected behaviour - - - -## Log files - -Have you provided the following extra information/files: - -- [ ] The command used to run the pipeline -- [ ] The `.nextflow.log` file - -## System - -- Hardware: -- Executor: -- OS: -- Version - -## Nextflow Installation - -- Version: - -## Container engine - -- Engine: -- version: - -## Additional context - - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..0e11dc1f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,52 @@ + +name: Bug report +description: Report something that is broken or incorrect +labels: bug +body: + + - type: markdown + attributes: + value: | + Before you post this issue, please check the documentation: + + - [nf-core website: troubleshooting](https://nf-co.re/usage/troubleshooting) + - [nf-core/cutandrun pipeline documentation](https://nf-co.re/cutandrun/usage) + + - type: textarea + id: description + attributes: + label: Description of the bug + description: A clear and concise description of what the bug is. + validations: + required: true + + - type: textarea + id: command_used + attributes: + label: Command used and terminal output + description: Steps to reproduce the behaviour. Please paste the command you used to launch the pipeline and the output from your terminal. + render: console + placeholder: | + $ nextflow run ... + + Some output where something broke + + - type: textarea + id: files + attributes: + label: Relevant files + description: | + Please drag and drop the relevant files here. Create a `.zip` archive if the extension is not allowed. + Your verbose log file `.nextflow.log` is often useful _(this is a hidden file in the directory where you launched the pipeline)_ as well as custom Nextflow configuration files. + + - type: textarea + id: system + attributes: + label: System information + description: | + * Nextflow version _(eg. 21.10.3)_ + * Hardware _(eg. HPC, Desktop, Cloud)_ + * Executor _(eg. slurm, local, awsbatch)_ + * Container engine: _(e.g. Docker, Singularity, Conda, Podman, Shifter or Charliecloud)_ + * OS _(eg. CentOS Linux, macOS, Linux Mint)_ + * Version of nf-core/cutandrun _(eg. 1.1, 1.5, 1.8.2)_ diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index f78ab65d..ab10753e 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,3 @@ -blank_issues_enabled: false contact_links: - name: Join nf-core url: https://nf-co.re/join diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 59a8ac75..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for the nf-core/cutandrun pipeline -labels: enhancement ---- - - - -## Is your feature request related to a problem? Please describe - - - - - -## Describe the solution you'd like - - - -## Describe alternatives you've considered - - - -## Additional context - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..5ace88de --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,11 @@ +name: Feature request +description: Suggest an idea for the nf-core/cutandrun pipeline +labels: enhancement +body: + - type: textarea + id: description + attributes: + label: Description of feature + description: Please describe your suggestion for a new feature. It might help to describe a problem or use case, plus any alternatives that you have considered. + validations: + required: true diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index ca0703d5..9029961c 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -14,14 +14,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Launch workflow via tower - uses: nf-core/tower-action@master + uses: nf-core/tower-action@v2 # TODO nf-core: You can customise AWS full pipeline tests as required # Add full size test data (but still relatively small datasets for few samples) # on the `test_full.config` test runs with only one set of parameters with: workspace_id: ${{ secrets.TOWER_WORKSPACE_ID }} - bearer_token: ${{ secrets.TOWER_BEARER_TOKEN }} + access_token: ${{ secrets.TOWER_ACCESS_TOKEN }} compute_env: ${{ secrets.TOWER_COMPUTE_ENV }} pipeline: ${{ github.repository }} revision: ${{ github.sha }} @@ -30,5 +30,5 @@ jobs: { "outdir": "s3://${{ secrets.AWS_S3_BUCKET }}/cutandrun/results-${{ github.sha }}" } - profiles: '[ "test_full", "aws_tower" ]' - + profiles: test_full,aws_tower + pre_run_script: 'export NXF_VER=21.10.3' diff --git a/.github/workflows/awstest.yml b/.github/workflows/awstest.yml index 3725fd35..ad27798e 100644 --- a/.github/workflows/awstest.yml +++ b/.github/workflows/awstest.yml @@ -11,18 +11,18 @@ jobs: runs-on: ubuntu-latest steps: - name: Launch workflow via tower - uses: nf-core/tower-action@master + uses: nf-core/tower-action@v2 with: workspace_id: ${{ secrets.TOWER_WORKSPACE_ID }} - bearer_token: ${{ secrets.TOWER_BEARER_TOKEN }} + access_token: ${{ secrets.TOWER_ACCESS_TOKEN }} compute_env: ${{ secrets.TOWER_COMPUTE_ENV }} pipeline: ${{ github.repository }} revision: ${{ github.sha }} workdir: s3://${{ secrets.AWS_S3_BUCKET }}/work/cutandrun/work-${{ github.sha }} parameters: | { - "outdir": "s3://${{ secrets.AWS_S3_BUCKET }}/cutandrun/results-${{ github.sha }}" + "outdir": "s3://${{ secrets.AWS_S3_BUCKET }}/cutandrun/results-test-${{ github.sha }}" } - profiles: '[ "test", "aws_tower" ]' - + profiles: test,aws_tower + pre_run_script: 'export NXF_VER=21.10.3' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2d81d82..d6e2688f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,8 +8,9 @@ on: release: types: [published] -# Uncomment if we need an edge release of Nextflow again -# env: NXF_EDGE: 1 +env: + NXF_ANSI_LOG: false + CAPSULE_LOG: none jobs: test: @@ -17,20 +18,26 @@ jobs: # Only run on push if this is the nf-core dev branch (merged PRs) if: ${{ github.event_name != 'push' || (github.event_name == 'push' && github.repository == 'nf-core/cutandrun') }} runs-on: ubuntu-latest - env: - NXF_VER: ${{ matrix.nxf_ver }} - NXF_ANSI_LOG: false strategy: matrix: - # Nextflow versions: check pipeline minimum and current latest - nxf_ver: ['21.04.0', ''] + # Nextflow versions + include: + # Test pipeline minimum Nextflow version + - NXF_VER: '21.10.3' + NXF_EDGE: '' + # Test latest edge release of Nextflow + - NXF_VER: '' + NXF_EDGE: '1' steps: - name: Check out pipeline code uses: actions/checkout@v2 - name: Install Nextflow env: - CAPSULE_LOG: none + NXF_VER: ${{ matrix.NXF_VER }} + # Uncomment only if the edge release is more recent than the latest stable release + # See https://github.com/nextflow-io/nextflow/issues/2467 + # NXF_EDGE: ${{ matrix.NXF_EDGE }} run: | wget -qO- get.nextflow.io | bash sudo mv nextflow /usr/local/bin/ diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index 90f03c6f..44d72994 100644 --- a/.github/workflows/linting_comment.yml +++ b/.github/workflows/linting_comment.yml @@ -15,6 +15,7 @@ jobs: uses: dawidd6/action-download-artifact@v2 with: workflow: linting.yml + workflow_conclusion: completed - name: Get PR number id: pr_number diff --git a/CITATIONS.md b/CITATIONS.md index 58576425..bc28ae31 100644 --- a/CITATIONS.md +++ b/CITATIONS.md @@ -12,7 +12,7 @@ * [FastQC](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/) -* [MultiQC](https://www.ncbi.nlm.nih.gov/pubmed/27312411/) +* [MultiQC](https://pubmed.ncbi.nlm.nih.gov/27312411/) > Ewels P, Magnusson M, Lundin S, Käller M. MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics. 2016 Oct 1;32(19):3047-8. doi: 10.1093/bioinformatics/btw354. Epub 2016 Jun 16. PubMed PMID: 27312411; PubMed Central PMCID: PMC5039924. ## Software packaging/containerisation tools diff --git a/README.md b/README.md index 2c8b3a96..3e31011f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# ![nf-core/cutandrun](docs/images/nf-core-cutandrun_logo.png) +# ![nf-core/cutandrun](docs/images/nf-core-cutandrun_logo_light.png#gh-light-mode-only) ![nf-core/cutandrun](docs/images/nf-core-cutandrun_logo_dark.png#gh-dark-mode-only) [![GitHub Actions CI Status](https://github.com/nf-core/cutandrun/workflows/nf-core%20CI/badge.svg)](https://github.com/nf-core/cutandrun/actions?query=workflow%3A%22nf-core+CI%22) [![GitHub Actions Linting Status](https://github.com/nf-core/cutandrun/workflows/nf-core%20linting/badge.svg)](https://github.com/nf-core/cutandrun/actions?query=workflow%3A%22nf-core+linting%22) [![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/cutandrun/results) [![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX) -[![Nextflow](https://img.shields.io/badge/nextflow%20DSL2-%E2%89%A521.04.0-23aa62.svg?labelColor=000000)](https://www.nextflow.io/) +[![Nextflow](https://img.shields.io/badge/nextflow%20DSL2-%E2%89%A521.10.3-23aa62.svg?labelColor=000000)](https://www.nextflow.io/) [![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) [![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) [![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) @@ -33,18 +33,21 @@ On release, automated continuous integration tests run the pipeline on a full-si ## Quick Start -1. Install [`Nextflow`](https://www.nextflow.io/docs/latest/getstarted.html#installation) (`>=21.04.0`) +1. Install [`Nextflow`](https://www.nextflow.io/docs/latest/getstarted.html#installation) (`>=21.10.3`) 2. Install any of [`Docker`](https://docs.docker.com/engine/installation/), [`Singularity`](https://www.sylabs.io/guides/3.0/user-guide/), [`Podman`](https://podman.io/), [`Shifter`](https://nersc.gitlab.io/development/shifter/how-to-use/) or [`Charliecloud`](https://hpc.github.io/charliecloud/) for full pipeline reproducibility _(please only use [`Conda`](https://conda.io/miniconda.html) as a last resort; see [docs](https://nf-co.re/usage/configuration#basic-configuration-profiles))_ 3. Download the pipeline and test it on a minimal dataset with a single command: ```console - nextflow run nf-core/cutandrun -profile test, + nextflow run nf-core/cutandrun -profile test,YOURPROFILE ``` + Note that some form of configuration will be needed so that Nextflow knows how to fetch the required software. This is usually done in the form of a config profile (`YOURPROFILE` in the example command above). You can chain multiple config profiles in a comma-separated string. + + > * The pipeline comes with config profiles called `docker`, `singularity`, `podman`, `shifter`, `charliecloud` and `conda` which instruct the pipeline to use the named tool for software management. For example, `-profile test,docker`. > * Please check [nf-core/configs](https://github.com/nf-core/configs#documentation) to see if a custom config file to run nf-core pipelines already exists for your Institute. If so, you can simply use `-profile ` in your command. This will enable either `docker` or `singularity` and set the appropriate execution settings for your local compute environment. - > * If you are using `singularity` then the pipeline will auto-detect this and attempt to download the Singularity images directly as opposed to performing a conversion from Docker images. If you are persistently observing issues downloading Singularity images directly due to timeout or network issues then please use the `--singularity_pull_docker_container` parameter to pull and convert the Docker image instead. Alternatively, it is highly recommended to use the [`nf-core download`](https://nf-co.re/tools/#downloading-pipelines-for-offline-use) command to pre-download all of the required containers before running the pipeline and to set the [`NXF_SINGULARITY_CACHEDIR` or `singularity.cacheDir`](https://www.nextflow.io/docs/latest/singularity.html?#singularity-docker-hub) Nextflow options to be able to store and re-use the images from a central location for future pipeline runs. + > * If you are using `singularity` and are persistently observing issues downloading Singularity images directly due to timeout or network issues, then you can use the `--singularity_pull_docker_container` parameter to pull and convert the Docker image instead. Alternatively, you can use the [`nf-core download`](https://nf-co.re/tools/#downloading-pipelines-for-offline-use) command to download images first, before running the pipeline. Setting the [`NXF_SINGULARITY_CACHEDIR` or `singularity.cacheDir`](https://www.nextflow.io/docs/latest/singularity.html?#singularity-docker-hub) Nextflow options enables you to store and re-use the images from a central location for future pipeline runs. > * If you are using `conda`, it is highly recommended to use the [`NXF_CONDA_CACHEDIR` or `conda.cacheDir`](https://www.nextflow.io/docs/latest/conda.html) settings to store the environments in a central location for future pipeline runs. 4. Start running your own analysis! diff --git a/assets/multiqc_config.yaml b/assets/multiqc_config.yaml index bc24430e..0c1502b7 100644 --- a/assets/multiqc_config.yaml +++ b/assets/multiqc_config.yaml @@ -1,7 +1,7 @@ report_comment: > This report has been generated by the nf-core/cutandrun analysis pipeline. For information about how to interpret these results, please see the - documentation. + documentation. report_section_order: software_versions: order: -1000 diff --git a/assets/nf-core-cutandrun_logo.png b/assets/nf-core-cutandrun_logo.png deleted file mode 100644 index cd4c3d47369672c124d6b464b3a11bd8e6debe8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18251 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

}Yfz%G??j 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 bk9g4^PJWh%?s|i#<3L`^s>+m083+D9U^WyR diff --git a/assets/nf-core-cutandrun_logo_light.png b/assets/nf-core-cutandrun_logo_light.png new file mode 100644 index 00000000..dee3db82 --- /dev/null +++ b/assets/nf-core-cutandrun_logo_light.png @@ -0,0 +1,9 @@ + + + + 508 Resource Limit Is Reached + +

Resource Limit Is Reached

+The website is temporarily unable to service your request as it exceeded resource limit. +Please try again later. + diff --git a/assets/sendmail_template.txt b/assets/sendmail_template.txt index 64034571..d62ff2b1 100644 --- a/assets/sendmail_template.txt +++ b/assets/sendmail_template.txt @@ -12,9 +12,9 @@ $email_html Content-Type: image/png;name="nf-core-cutandrun_logo.png" Content-Transfer-Encoding: base64 Content-ID: -Content-Disposition: inline; filename="nf-core-cutandrun_logo.png" +Content-Disposition: inline; filename="nf-core-cutandrun_logo_light.png" -<% out << new File("$projectDir/assets/nf-core-cutandrun_logo.png"). +<% out << new File("$projectDir/assets/nf-core-cutandrun_logo_light.png"). bytes. encodeBase64(). toString(). diff --git a/bin/scrape_software_versions.py b/bin/scrape_software_versions.py deleted file mode 100755 index b87f78a2..00000000 --- a/bin/scrape_software_versions.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function -import os - -results = {} -version_files = [x for x in os.listdir(".") if x.endswith(".version.txt")] -for version_file in version_files: - - software = version_file.replace(".version.txt", "") - if software == "pipeline": - software = "nf-core/cutandrun" - - with open(version_file) as fin: - version = fin.read().strip() - results[software] = version - -# Dump to YAML -print( - """ -id: 'software_versions' -section_name: 'nf-core/cutandrun Software Versions' -section_href: 'https://github.com/nf-core/cutandrun' -plot_type: 'html' -description: 'are collected at run time from the software output.' -data: | -
-""" -) -for k, v in sorted(results.items()): - print("
{}
{}
".format(k, v)) -print("
") - -# Write out as tsv file: -with open("software_versions.tsv", "w") as f: - for k, v in sorted(results.items()): - f.write("{}\t{}\n".format(k, v)) diff --git a/conf/base.config b/conf/base.config index fd108bf1..2752abab 100644 --- a/conf/base.config +++ b/conf/base.config @@ -54,4 +54,7 @@ process { errorStrategy = 'retry' maxRetries = 2 } + withName:CUSTOM_DUMPSOFTWAREVERSIONS { + cache = false + } } diff --git a/conf/modules.config b/conf/modules.config index 0b1bfdec..a0506a4d 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -1,32 +1,41 @@ /* ======================================================================================== - Config file for defining DSL2 per module options + Config file for defining DSL2 per module options and publishing paths ======================================================================================== Available keys to override module options: - args = Additional arguments appended to command in module. - args2 = Second set of arguments appended to command in module (multi-tool modules). - args3 = Third set of arguments appended to command in module (multi-tool modules). - publish_dir = Directory to publish results. - publish_by_meta = Groovy list of keys available in meta map to append as directories to "publish_dir" path - If publish_by_meta = true - Value of ${meta['id']} is appended as a directory to "publish_dir" path - If publish_by_meta = ['id', 'custompath'] - If "id" is in meta map and "custompath" isn't then "${meta['id']}/custompath/" - is appended as a directory to "publish_dir" path - If publish_by_meta = false / null - No directories are appended to "publish_dir" path - publish_files = Groovy map where key = "file_ext" and value = "directory" to publish results for that file extension - The value of "directory" is appended to the standard "publish_dir" path as defined above. - If publish_files = null (unspecified) - All files are published. - If publish_files = false - No files are published. - suffix = File name suffix for output files. + ext.args = Additional arguments appended to command in module. + ext.args2 = Second set of arguments appended to command in module (multi-tool modules). + ext.args3 = Third set of arguments appended to command in module (multi-tool modules). + ext.prefix = File name prefix for output files. ---------------------------------------------------------------------------------------- */ -params { - modules { - 'fastqc' { - args = "--quiet" - } - 'multiqc' { - args = "" - } +process { + + publishDir = [ + path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, + mode: 'copy', + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + + withName: SAMPLESHEET_CHECK { + publishDir = [ + path: { "${params.outdir}/pipeline_info" }, + mode: 'copy', + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + + withName: FASTQC { + ext.args = '--quiet' } + + withName: CUSTOM_DUMPSOFTWAREVERSIONS { + publishDir = [ + path: { "${params.outdir}/pipeline_info" }, + mode: 'copy', + pattern: '*_versions.yml' + ] + } + } diff --git a/conf/test.config b/conf/test.config index cd73b7be..7add5c84 100644 --- a/conf/test.config +++ b/conf/test.config @@ -16,8 +16,8 @@ params { // Limit resources so that this can run on GitHub Actions max_cpus = 2 - max_memory = 6.GB - max_time = 6.h + max_memory = '6.GB' + max_time = '6.h' // Input data // TODO nf-core: Specify the paths to your test data on nf-core/test-datasets diff --git a/docs/images/nf-core-cutandrun_logo.png b/docs/images/nf-core-cutandrun_logo.png deleted file mode 100644 index 7e8366961b8371a440683f918d0eb8b6284c231a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34713 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 + +503 Service Unavailable + +

Service Unavailable

+

The server is temporarily unable to service your +request due to maintenance downtime or capacity +problems. Please try again later.

+

Additionally, a 503 Service Unavailable +error was encountered while trying to use an ErrorDocument to handle the request.

+ diff --git a/docs/images/nf-core-cutandrun_logo_light.png b/docs/images/nf-core-cutandrun_logo_light.png new file mode 100644 index 00000000..1993002f --- /dev/null +++ b/docs/images/nf-core-cutandrun_logo_light.png @@ -0,0 +1,13 @@ + + +507 Insufficient Storage + +

Insufficient Storage

+

The method could not be performed on the resource +because the server is unable to store the +representation needed to successfully complete the +request. There is insufficient free space left in +your storage allocation.

+

Additionally, a 507 Insufficient Storage +error was encountered while trying to use an ErrorDocument to handle the request.

+ diff --git a/docs/output.md b/docs/output.md index be86961a..6a290ff6 100644 --- a/docs/output.md +++ b/docs/output.md @@ -60,7 +60,7 @@ Results generated by MultiQC collate pipeline QC from supported tools e.g. FastQ * `pipeline_info/` * Reports generated by Nextflow: `execution_report.html`, `execution_timeline.html`, `execution_trace.txt` and `pipeline_dag.dot`/`pipeline_dag.svg`. - * Reports generated by the pipeline: `pipeline_report.html`, `pipeline_report.txt` and `software_versions.tsv`. + * Reports generated by the pipeline: `pipeline_report.html`, `pipeline_report.txt` and `software_versions.yml`. The `pipeline_report*` files will only be present if the `--email` / `--email_on_fail` parameter's are used when running the pipeline. * Reformatted samplesheet files used as input to the pipeline: `samplesheet.valid.csv`. diff --git a/docs/usage.md b/docs/usage.md index 9e243bdb..929fbbda 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -181,42 +181,6 @@ process { > **NB:** We specify just the process name i.e. `STAR_ALIGN` in the config file and not the full task name string that is printed to screen in the error message or on the terminal whilst the pipeline is running i.e. `RNASEQ:ALIGN_STAR:STAR_ALIGN`. You may get a warning suggesting that the process selector isn't recognised but you can ignore that if the process name has been specified correctly. This is something that needs to be fixed upstream in core Nextflow. -### Tool-specific options - -For the ultimate flexibility, we have implemented and are using Nextflow DSL2 modules in a way where it is possible for both developers and users to change tool-specific command-line arguments (e.g. providing an additional command-line argument to the `STAR_ALIGN` process) as well as publishing options (e.g. saving files produced by the `STAR_ALIGN` process that aren't saved by default by the pipeline). In the majority of instances, as a user you won't have to change the default options set by the pipeline developer(s), however, there may be edge cases where creating a simple custom config file can improve the behaviour of the pipeline if for example it is failing due to a weird error that requires setting a tool-specific parameter to deal with smaller / larger genomes. - -The command-line arguments passed to STAR in the `STAR_ALIGN` module are a combination of: - -* Mandatory arguments or those that need to be evaluated within the scope of the module, as supplied in the [`script`](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/modules/nf-core/software/star/align/main.nf#L49-L55) section of the module file. - -* An [`options.args`](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/modules/nf-core/software/star/align/main.nf#L56) string of non-mandatory parameters that is set to be empty by default in the module but can be overwritten when including the module in the sub-workflow / workflow context via the `addParams` Nextflow option. - -The nf-core/rnaseq pipeline has a sub-workflow (see [terminology](https://github.com/nf-core/modules#terminology)) specifically to align reads with STAR and to sort, index and generate some basic stats on the resulting BAM files using SAMtools. At the top of this file we import the `STAR_ALIGN` module via the Nextflow [`include`](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/subworkflows/nf-core/align_star.nf#L10) keyword and by default the options passed to the module via the `addParams` option are set as an empty Groovy map [here](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/subworkflows/nf-core/align_star.nf#L5); this in turn means `options.args` will be set to empty by default in the module file too. This is an intentional design choice and allows us to implement well-written sub-workflows composed of a chain of tools that by default run with the bare minimum parameter set for any given tool in order to make it much easier to share across pipelines and to provide the flexibility for users and developers to customise any non-mandatory arguments. - -When including the sub-workflow above in the main pipeline workflow we use the same `include` statement, however, we now have the ability to overwrite options for each of the tools in the sub-workflow including the [`align_options`](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/workflows/rnaseq.nf#L225) variable that will be used specifically to overwrite the optional arguments passed to the `STAR_ALIGN` module. In this case, the options to be provided to `STAR_ALIGN` have been assigned sensible defaults by the developer(s) in the pipeline's [`modules.config`](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/conf/modules.config#L70-L74) and can be accessed and customised in the [workflow context](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/workflows/rnaseq.nf#L201-L204) too before eventually passing them to the sub-workflow as a Groovy map called `star_align_options`. These options will then be propagated from `workflow -> sub-workflow -> module`. - -As mentioned at the beginning of this section it may also be necessary for users to overwrite the options passed to modules to be able to customise specific aspects of the way in which a particular tool is executed by the pipeline. Given that all of the default module options are stored in the pipeline's `modules.config` as a [`params` variable](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/conf/modules.config#L24-L25) it is also possible to overwrite any of these options via a custom config file. - -Say for example we want to append an additional, non-mandatory parameter (i.e. `--outFilterMismatchNmax 16`) to the arguments passed to the `STAR_ALIGN` module. Firstly, we need to copy across the default `args` specified in the [`modules.config`](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/conf/modules.config#L71) and create a custom config file that is a composite of the default `args` as well as the additional options you would like to provide. This is very important because Nextflow will overwrite the default value of `args` that you provide via the custom config. - -As you will see in the example below, we have: - -* appended `--outFilterMismatchNmax 16` to the default `args` used by the module. -* changed the default `publish_dir` value to where the files will eventually be published in the main results directory. -* appended `'bam':''` to the default value of `publish_files` so that the BAM files generated by the process will also be saved in the top-level results directory for the module. Note: `'out':'log'` means any file/directory ending in `out` will now be saved in a separate directory called `my_star_directory/log/`. - -```nextflow -params { - modules { - 'star_align' { - args = "--quantMode TranscriptomeSAM --twopassMode Basic --outSAMtype BAM Unsorted --readFilesCommand zcat --runRNGseed 0 --outFilterMultimapNmax 20 --alignSJDBoverhangMin 1 --outSAMattributes NH HI AS NM MD --quantTranscriptomeBan Singleend --outFilterMismatchNmax 16" - publish_dir = "my_star_directory" - publish_files = ['out':'log', 'tab':'log', 'bam':''] - } - } -} -``` - ### Updating containers The [Nextflow DSL2](https://www.nextflow.io/docs/latest/dsl2.html) implementation of this pipeline uses one container per process which makes it much easier to maintain and update software dependencies. If for some reason you need to use a different version of a particular tool with the pipeline then you just need to identify the `process` name and override the Nextflow `container` definition for that process using the `withName` declaration. For example, in the [nf-core/viralrecon](https://nf-co.re/viralrecon) pipeline a tool called [Pangolin](https://github.com/cov-lineages/pangolin) has been used during the COVID-19 pandemic to assign lineages to SARS-CoV-2 genome sequenced samples. Given that the lineage assignments change quite frequently it doesn't make sense to re-release the nf-core/viralrecon everytime a new version of Pangolin has been released. However, you can override the default container used by the pipeline by creating a custom config file and passing it as a command-line argument via `-c custom.config`. diff --git a/lib/NfcoreSchema.groovy b/lib/NfcoreSchema.groovy index 8d6920dd..40ab65f2 100755 --- a/lib/NfcoreSchema.groovy +++ b/lib/NfcoreSchema.groovy @@ -105,9 +105,13 @@ class NfcoreSchema { // Collect expected parameters from the schema def expectedParams = [] + def enums = [:] for (group in schemaParams) { for (p in group.value['properties']) { expectedParams.push(p.key) + if (group.value['properties'][p.key].containsKey('enum')) { + enums[p.key] = group.value['properties'][p.key]['enum'] + } } } @@ -155,7 +159,7 @@ class NfcoreSchema { println '' log.error 'ERROR: Validation of pipeline parameters failed!' JSONObject exceptionJSON = e.toJSON() - printExceptions(exceptionJSON, params_json, log) + printExceptions(exceptionJSON, params_json, log, enums) println '' has_error = true } @@ -202,7 +206,7 @@ class NfcoreSchema { } def type = '[' + group_params.get(param).type + ']' def description = group_params.get(param).description - def defaultValue = group_params.get(param).default ? " [default: " + group_params.get(param).default.toString() + "]" : '' + def defaultValue = group_params.get(param).default != null ? " [default: " + group_params.get(param).default.toString() + "]" : '' def description_default = description + colors.dim + defaultValue + colors.reset // Wrap long description texts // Loosely based on https://dzone.com/articles/groovy-plain-text-word-wrap @@ -260,13 +264,12 @@ class NfcoreSchema { // Get pipeline parameters defined in JSON Schema def Map params_summary = [:] - def blacklist = ['hostnames'] def params_map = paramsLoad(getSchemaPath(workflow, schema_filename=schema_filename)) for (group in params_map.keySet()) { def sub_params = new LinkedHashMap() def group_params = params_map.get(group) // This gets the parameters of that particular group for (param in group_params.keySet()) { - if (params.containsKey(param) && !blacklist.contains(param)) { + if (params.containsKey(param)) { def params_value = params.get(param) def schema_value = group_params.get(param).default def param_type = group_params.get(param).type @@ -330,7 +333,7 @@ class NfcoreSchema { // // Loop over nested exceptions and print the causingException // - private static void printExceptions(ex_json, params_json, log) { + private static void printExceptions(ex_json, params_json, log, enums, limit=5) { def causingExceptions = ex_json['causingExceptions'] if (causingExceptions.length() == 0) { def m = ex_json['message'] =~ /required key \[([^\]]+)\] not found/ @@ -346,11 +349,20 @@ class NfcoreSchema { else { def param = ex_json['pointerToViolation'] - ~/^#\// def param_val = params_json[param].toString() - log.error "* --${param}: ${ex_json['message']} (${param_val})" + if (enums.containsKey(param)) { + def error_msg = "* --${param}: '${param_val}' is not a valid choice (Available choices" + if (enums[param].size() > limit) { + log.error "${error_msg} (${limit} of ${enums[param].size()}): ${enums[param][0..limit-1].join(', ')}, ... )" + } else { + log.error "${error_msg}: ${enums[param].join(', ')})" + } + } else { + log.error "* --${param}: ${ex_json['message']} (${param_val})" + } } } for (ex in causingExceptions) { - printExceptions(ex, params_json, log) + printExceptions(ex, params_json, log, enums) } } diff --git a/lib/NfcoreTemplate.groovy b/lib/NfcoreTemplate.groovy index 44551e0a..2fc0a9b9 100755 --- a/lib/NfcoreTemplate.groovy +++ b/lib/NfcoreTemplate.groovy @@ -19,27 +19,16 @@ class NfcoreTemplate { } // - // Check params.hostnames + // Warn if a -profile or Nextflow config has not been provided to run the pipeline // - public static void hostName(workflow, params, log) { - Map colors = logColours(params.monochrome_logs) - if (params.hostnames) { - try { - def hostname = "hostname".execute().text.trim() - params.hostnames.each { prof, hnames -> - hnames.each { hname -> - if (hostname.contains(hname) && !workflow.profile.contains(prof)) { - log.info "=${colors.yellow}====================================================${colors.reset}=\n" + - "${colors.yellow}WARN: You are running with `-profile $workflow.profile`\n" + - " but your machine hostname is ${colors.white}'$hostname'${colors.reset}.\n" + - " ${colors.yellow_bold}Please use `-profile $prof${colors.reset}`\n" + - "=${colors.yellow}====================================================${colors.reset}=" - } - } - } - } catch (Exception e) { - log.warn "[$workflow.manifest.name] Could not determine 'hostname' - skipping check. Reason: ${e.message}." - } + public static void checkConfigProvided(workflow, log) { + if (workflow.profile == 'standard' && workflow.configFiles.size() <= 1) { + log.warn "[$workflow.manifest.name] You are attempting to run the pipeline without any custom configuration!\n\n" + + "This will be dependent on your local compute environment but can be achieved via one or more of the following:\n" + + " (1) Using an existing pipeline profile e.g. `-profile docker` or `-profile singularity`\n" + + " (2) Using an existing nf-core/configs for your Institution e.g. `-profile crick` or `-profile uppmax`\n" + + " (3) Using your own local custom config e.g. `-c /path/to/your/custom.config`\n\n" + + "Please refer to the quick start section and usage docs for the pipeline.\n " } } @@ -168,7 +157,6 @@ class NfcoreTemplate { log.info "-${colors.purple}[$workflow.manifest.name]${colors.red} Pipeline completed successfully, but with errored process(es) ${colors.reset}-" } } else { - hostName(workflow, params, log) log.info "-${colors.purple}[$workflow.manifest.name]${colors.red} Pipeline completed with errors${colors.reset}-" } } diff --git a/lib/Utils.groovy b/lib/Utils.groovy index 18173e98..1b88aec0 100755 --- a/lib/Utils.groovy +++ b/lib/Utils.groovy @@ -37,11 +37,4 @@ class Utils { "===================================================================================" } } - - // - // Join module args with appropriate spacing - // - public static String joinModuleArgs(args_list) { - return ' ' + args_list.join(' ') - } } diff --git a/lib/WorkflowMain.groovy b/lib/WorkflowMain.groovy index 8aa7e443..891a17fe 100755 --- a/lib/WorkflowMain.groovy +++ b/lib/WorkflowMain.groovy @@ -61,6 +61,9 @@ class WorkflowMain { // Print parameter summary log to screen log.info paramsSummaryLog(workflow, params, log) + // Check that a -profile or Nextflow config has been provided to run the pipeline + NfcoreTemplate.checkConfigProvided(workflow, log) + // Check that conda channels are set-up correctly if (params.enable_conda) { Utils.checkCondaChannels(log) @@ -69,9 +72,6 @@ class WorkflowMain { // Check AWS batch settings NfcoreTemplate.awsBatch(workflow, params) - // Check the hostnames against configured profiles - NfcoreTemplate.hostName(workflow, params, log) - // Check input has been provided if (!params.input) { log.error "Please provide an input samplesheet to the pipeline e.g. '--input samplesheet.csv'" diff --git a/modules.json b/modules.json index 91d5404c..6a39ea1b 100644 --- a/modules.json +++ b/modules.json @@ -3,12 +3,15 @@ "homePage": "https://github.com/nf-core/cutandrun", "repos": { "nf-core/modules": { + "custom/dumpsoftwareversions": { + "git_sha": "20d8250d9f39ddb05dfb437603aaf99b5c0b2b41" + }, "fastqc": { - "git_sha": "e937c7950af70930d1f34bb961403d9d2aa81c7d" + "git_sha": "9d0cad583b9a71a6509b754fdf589cbfbed08961" }, "multiqc": { - "git_sha": "e937c7950af70930d1f34bb961403d9d2aa81c7d" + "git_sha": "20d8250d9f39ddb05dfb437603aaf99b5c0b2b41" } } } -} +} \ No newline at end of file diff --git a/modules/local/functions.nf b/modules/local/functions.nf deleted file mode 100644 index da9da093..00000000 --- a/modules/local/functions.nf +++ /dev/null @@ -1,68 +0,0 @@ -// -// Utility functions used in nf-core DSL2 module files -// - -// -// Extract name of software tool from process name using $task.process -// -def getSoftwareName(task_process) { - return task_process.tokenize(':')[-1].tokenize('_')[0].toLowerCase() -} - -// -// Function to initialise default values and to generate a Groovy Map of available options for nf-core modules -// -def initOptions(Map args) { - def Map options = [:] - options.args = args.args ?: '' - options.args2 = args.args2 ?: '' - options.args3 = args.args3 ?: '' - options.publish_by_meta = args.publish_by_meta ?: [] - options.publish_dir = args.publish_dir ?: '' - options.publish_files = args.publish_files - options.suffix = args.suffix ?: '' - return options -} - -// -// Tidy up and join elements of a list to return a path string -// -def getPathFromList(path_list) { - def paths = path_list.findAll { item -> !item?.trim().isEmpty() } // Remove empty entries - paths = paths.collect { it.trim().replaceAll("^[/]+|[/]+\$", "") } // Trim whitespace and trailing slashes - return paths.join('/') -} - -// -// Function to save/publish module results -// -def saveFiles(Map args) { - if (!args.filename.endsWith('.version.txt')) { - def ioptions = initOptions(args.options) - def path_list = [ ioptions.publish_dir ?: args.publish_dir ] - if (ioptions.publish_by_meta) { - def key_list = ioptions.publish_by_meta instanceof List ? ioptions.publish_by_meta : args.publish_by_meta - for (key in key_list) { - if (args.meta && key instanceof String) { - def path = key - if (args.meta.containsKey(key)) { - path = args.meta[key] instanceof Boolean ? "${key}_${args.meta[key]}".toString() : args.meta[key] - } - path = path instanceof String ? path : '' - path_list.add(path) - } - } - } - if (ioptions.publish_files instanceof Map) { - for (ext in ioptions.publish_files) { - if (args.filename.endsWith(ext.key)) { - def ext_list = path_list.collect() - ext_list.add(ext.value) - return "${getPathFromList(ext_list)}/$args.filename" - } - } - } else if (ioptions.publish_files == null) { - return "${getPathFromList(path_list)}/$args.filename" - } - } -} diff --git a/modules/local/get_software_versions.nf b/modules/local/get_software_versions.nf deleted file mode 100644 index 6e7a0c23..00000000 --- a/modules/local/get_software_versions.nf +++ /dev/null @@ -1,33 +0,0 @@ -// Import generic module functions -include { saveFiles } from './functions' - -params.options = [:] - -process GET_SOFTWARE_VERSIONS { - publishDir "${params.outdir}", - mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:'pipeline_info', meta:[:], publish_by_meta:[]) } - - conda (params.enable_conda ? "conda-forge::python=3.8.3" : null) - if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) { - container "https://depot.galaxyproject.org/singularity/python:3.8.3" - } else { - container "quay.io/biocontainers/python:3.8.3" - } - - cache false - - input: - path versions - - output: - path "software_versions.tsv" , emit: tsv - path 'software_versions_mqc.yaml', emit: yaml - - script: // This script is bundled with the pipeline, in nf-core/cutandrun/bin/ - """ - echo $workflow.manifest.version > pipeline.version.txt - echo $workflow.nextflow.version > nextflow.version.txt - scrape_software_versions.py &> software_versions_mqc.yaml - """ -} diff --git a/modules/local/samplesheet_check.nf b/modules/local/samplesheet_check.nf index 71853fb4..c320b1e8 100644 --- a/modules/local/samplesheet_check.nf +++ b/modules/local/samplesheet_check.nf @@ -1,31 +1,27 @@ -// Import generic module functions -include { saveFiles } from './functions' - -params.options = [:] - process SAMPLESHEET_CHECK { tag "$samplesheet" - publishDir "${params.outdir}", - mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:'pipeline_info', meta:[:], publish_by_meta:[]) } conda (params.enable_conda ? "conda-forge::python=3.8.3" : null) - if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) { - container "https://depot.galaxyproject.org/singularity/python:3.8.3" - } else { - container "quay.io/biocontainers/python:3.8.3" - } + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/python:3.8.3' : + 'quay.io/biocontainers/python:3.8.3' }" input: path samplesheet output: - path '*.csv' + path '*.csv' , emit: csv + path "versions.yml", emit: versions script: // This script is bundled with the pipeline, in nf-core/cutandrun/bin/ """ check_samplesheet.py \\ $samplesheet \\ samplesheet.valid.csv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + python: \$(python --version | sed 's/Python //g') + END_VERSIONS """ } diff --git a/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf b/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf new file mode 100644 index 00000000..934bb467 --- /dev/null +++ b/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf @@ -0,0 +1,21 @@ +process CUSTOM_DUMPSOFTWAREVERSIONS { + label 'process_low' + + // Requires `pyyaml` which does not have a dedicated container but is in the MultiQC container + conda (params.enable_conda ? "bioconda::multiqc=1.11" : null) + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/multiqc:1.11--pyhdfd78af_0' : + 'quay.io/biocontainers/multiqc:1.11--pyhdfd78af_0' }" + + input: + path versions + + output: + path "software_versions.yml" , emit: yml + path "software_versions_mqc.yml", emit: mqc_yml + path "versions.yml" , emit: versions + + script: + def args = task.ext.args ?: '' + template 'dumpsoftwareversions.py' +} diff --git a/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml b/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml new file mode 100644 index 00000000..5b5b8a60 --- /dev/null +++ b/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml @@ -0,0 +1,34 @@ +name: custom_dumpsoftwareversions +description: Custom module used to dump software versions within the nf-core pipeline template +keywords: + - custom + - version +tools: + - custom: + description: Custom module used to dump software versions within the nf-core pipeline template + homepage: https://github.com/nf-core/tools + documentation: https://github.com/nf-core/tools + licence: ['MIT'] +input: + - versions: + type: file + description: YML file containing software versions + pattern: "*.yml" + +output: + - yml: + type: file + description: Standard YML file containing software versions + pattern: "software_versions.yml" + - mqc_yml: + type: file + description: MultiQC custom content YML file containing software versions + pattern: "software_versions_mqc.yml" + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + +authors: + - "@drpatelh" + - "@grst" diff --git a/modules/nf-core/modules/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py b/modules/nf-core/modules/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py new file mode 100644 index 00000000..d1390392 --- /dev/null +++ b/modules/nf-core/modules/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python + +import yaml +import platform +from textwrap import dedent + + +def _make_versions_html(versions): + html = [ + dedent( + """\\ + + + + + + + + + + """ + ) + ] + for process, tmp_versions in sorted(versions.items()): + html.append("") + for i, (tool, version) in enumerate(sorted(tmp_versions.items())): + html.append( + dedent( + f"""\\ + + + + + + """ + ) + ) + html.append("") + html.append("
Process Name Software Version
{process if (i == 0) else ''}{tool}{version}
") + return "\\n".join(html) + + +versions_this_module = {} +versions_this_module["${task.process}"] = { + "python": platform.python_version(), + "yaml": yaml.__version__, +} + +with open("$versions") as f: + versions_by_process = yaml.load(f, Loader=yaml.BaseLoader) | versions_this_module + +# aggregate versions by the module name (derived from fully-qualified process name) +versions_by_module = {} +for process, process_versions in versions_by_process.items(): + module = process.split(":")[-1] + try: + assert versions_by_module[module] == process_versions, ( + "We assume that software versions are the same between all modules. " + "If you see this error-message it means you discovered an edge-case " + "and should open an issue in nf-core/tools. " + ) + except KeyError: + versions_by_module[module] = process_versions + +versions_by_module["Workflow"] = { + "Nextflow": "$workflow.nextflow.version", + "$workflow.manifest.name": "$workflow.manifest.version", +} + +versions_mqc = { + "id": "software_versions", + "section_name": "${workflow.manifest.name} Software Versions", + "section_href": "https://github.com/${workflow.manifest.name}", + "plot_type": "html", + "description": "are collected at run time from the software output.", + "data": _make_versions_html(versions_by_module), +} + +with open("software_versions.yml", "w") as f: + yaml.dump(versions_by_module, f, default_flow_style=False) +with open("software_versions_mqc.yml", "w") as f: + yaml.dump(versions_mqc, f, default_flow_style=False) + +with open("versions.yml", "w") as f: + yaml.dump(versions_this_module, f, default_flow_style=False) diff --git a/modules/nf-core/modules/fastqc/functions.nf b/modules/nf-core/modules/fastqc/functions.nf deleted file mode 100644 index da9da093..00000000 --- a/modules/nf-core/modules/fastqc/functions.nf +++ /dev/null @@ -1,68 +0,0 @@ -// -// Utility functions used in nf-core DSL2 module files -// - -// -// Extract name of software tool from process name using $task.process -// -def getSoftwareName(task_process) { - return task_process.tokenize(':')[-1].tokenize('_')[0].toLowerCase() -} - -// -// Function to initialise default values and to generate a Groovy Map of available options for nf-core modules -// -def initOptions(Map args) { - def Map options = [:] - options.args = args.args ?: '' - options.args2 = args.args2 ?: '' - options.args3 = args.args3 ?: '' - options.publish_by_meta = args.publish_by_meta ?: [] - options.publish_dir = args.publish_dir ?: '' - options.publish_files = args.publish_files - options.suffix = args.suffix ?: '' - return options -} - -// -// Tidy up and join elements of a list to return a path string -// -def getPathFromList(path_list) { - def paths = path_list.findAll { item -> !item?.trim().isEmpty() } // Remove empty entries - paths = paths.collect { it.trim().replaceAll("^[/]+|[/]+\$", "") } // Trim whitespace and trailing slashes - return paths.join('/') -} - -// -// Function to save/publish module results -// -def saveFiles(Map args) { - if (!args.filename.endsWith('.version.txt')) { - def ioptions = initOptions(args.options) - def path_list = [ ioptions.publish_dir ?: args.publish_dir ] - if (ioptions.publish_by_meta) { - def key_list = ioptions.publish_by_meta instanceof List ? ioptions.publish_by_meta : args.publish_by_meta - for (key in key_list) { - if (args.meta && key instanceof String) { - def path = key - if (args.meta.containsKey(key)) { - path = args.meta[key] instanceof Boolean ? "${key}_${args.meta[key]}".toString() : args.meta[key] - } - path = path instanceof String ? path : '' - path_list.add(path) - } - } - } - if (ioptions.publish_files instanceof Map) { - for (ext in ioptions.publish_files) { - if (args.filename.endsWith(ext.key)) { - def ext_list = path_list.collect() - ext_list.add(ext.value) - return "${getPathFromList(ext_list)}/$args.filename" - } - } - } else if (ioptions.publish_files == null) { - return "${getPathFromList(path_list)}/$args.filename" - } - } -} diff --git a/modules/nf-core/modules/fastqc/main.nf b/modules/nf-core/modules/fastqc/main.nf index 39c327b2..d250eca0 100644 --- a/modules/nf-core/modules/fastqc/main.nf +++ b/modules/nf-core/modules/fastqc/main.nf @@ -1,22 +1,11 @@ -// Import generic module functions -include { initOptions; saveFiles; getSoftwareName } from './functions' - -params.options = [:] -options = initOptions(params.options) - process FASTQC { tag "$meta.id" label 'process_medium' - publishDir "${params.outdir}", - mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getSoftwareName(task.process), meta:meta, publish_by_meta:['id']) } conda (params.enable_conda ? "bioconda::fastqc=0.11.9" : null) - if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) { - container "https://depot.galaxyproject.org/singularity/fastqc:0.11.9--0" - } else { - container "quay.io/biocontainers/fastqc:0.11.9--0" - } + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/fastqc:0.11.9--0' : + 'quay.io/biocontainers/fastqc:0.11.9--0' }" input: tuple val(meta), path(reads) @@ -24,24 +13,32 @@ process FASTQC { output: tuple val(meta), path("*.html"), emit: html tuple val(meta), path("*.zip") , emit: zip - path "*.version.txt" , emit: version + path "versions.yml" , emit: versions script: + def args = task.ext.args ?: '' // Add soft-links to original FastQs for consistent naming in pipeline - def software = getSoftwareName(task.process) - def prefix = options.suffix ? "${meta.id}${options.suffix}" : "${meta.id}" + def prefix = task.ext.prefix ?: "${meta.id}" if (meta.single_end) { """ [ ! -f ${prefix}.fastq.gz ] && ln -s $reads ${prefix}.fastq.gz - fastqc $options.args --threads $task.cpus ${prefix}.fastq.gz - fastqc --version | sed -e "s/FastQC v//g" > ${software}.version.txt + fastqc $args --threads $task.cpus ${prefix}.fastq.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + fastqc: \$( fastqc --version | sed -e "s/FastQC v//g" ) + END_VERSIONS """ } else { """ [ ! -f ${prefix}_1.fastq.gz ] && ln -s ${reads[0]} ${prefix}_1.fastq.gz [ ! -f ${prefix}_2.fastq.gz ] && ln -s ${reads[1]} ${prefix}_2.fastq.gz - fastqc $options.args --threads $task.cpus ${prefix}_1.fastq.gz ${prefix}_2.fastq.gz - fastqc --version | sed -e "s/FastQC v//g" > ${software}.version.txt + fastqc $args --threads $task.cpus ${prefix}_1.fastq.gz ${prefix}_2.fastq.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + fastqc: \$( fastqc --version | sed -e "s/FastQC v//g" ) + END_VERSIONS """ } } diff --git a/modules/nf-core/modules/fastqc/meta.yml b/modules/nf-core/modules/fastqc/meta.yml index 8eb9953d..b09553a3 100644 --- a/modules/nf-core/modules/fastqc/meta.yml +++ b/modules/nf-core/modules/fastqc/meta.yml @@ -15,6 +15,7 @@ tools: overrepresented sequences. homepage: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/ documentation: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/Help/ + licence: ['GPL-2.0-only'] input: - meta: type: map @@ -40,10 +41,10 @@ output: type: file description: FastQC report archive pattern: "*_{fastqc.zip}" - - version: + - versions: type: file - description: File containing software version - pattern: "*.{version.txt}" + description: File containing software versions + pattern: "versions.yml" authors: - "@drpatelh" - "@grst" diff --git a/modules/nf-core/modules/multiqc/functions.nf b/modules/nf-core/modules/multiqc/functions.nf deleted file mode 100644 index da9da093..00000000 --- a/modules/nf-core/modules/multiqc/functions.nf +++ /dev/null @@ -1,68 +0,0 @@ -// -// Utility functions used in nf-core DSL2 module files -// - -// -// Extract name of software tool from process name using $task.process -// -def getSoftwareName(task_process) { - return task_process.tokenize(':')[-1].tokenize('_')[0].toLowerCase() -} - -// -// Function to initialise default values and to generate a Groovy Map of available options for nf-core modules -// -def initOptions(Map args) { - def Map options = [:] - options.args = args.args ?: '' - options.args2 = args.args2 ?: '' - options.args3 = args.args3 ?: '' - options.publish_by_meta = args.publish_by_meta ?: [] - options.publish_dir = args.publish_dir ?: '' - options.publish_files = args.publish_files - options.suffix = args.suffix ?: '' - return options -} - -// -// Tidy up and join elements of a list to return a path string -// -def getPathFromList(path_list) { - def paths = path_list.findAll { item -> !item?.trim().isEmpty() } // Remove empty entries - paths = paths.collect { it.trim().replaceAll("^[/]+|[/]+\$", "") } // Trim whitespace and trailing slashes - return paths.join('/') -} - -// -// Function to save/publish module results -// -def saveFiles(Map args) { - if (!args.filename.endsWith('.version.txt')) { - def ioptions = initOptions(args.options) - def path_list = [ ioptions.publish_dir ?: args.publish_dir ] - if (ioptions.publish_by_meta) { - def key_list = ioptions.publish_by_meta instanceof List ? ioptions.publish_by_meta : args.publish_by_meta - for (key in key_list) { - if (args.meta && key instanceof String) { - def path = key - if (args.meta.containsKey(key)) { - path = args.meta[key] instanceof Boolean ? "${key}_${args.meta[key]}".toString() : args.meta[key] - } - path = path instanceof String ? path : '' - path_list.add(path) - } - } - } - if (ioptions.publish_files instanceof Map) { - for (ext in ioptions.publish_files) { - if (args.filename.endsWith(ext.key)) { - def ext_list = path_list.collect() - ext_list.add(ext.value) - return "${getPathFromList(ext_list)}/$args.filename" - } - } - } else if (ioptions.publish_files == null) { - return "${getPathFromList(path_list)}/$args.filename" - } - } -} diff --git a/modules/nf-core/modules/multiqc/main.nf b/modules/nf-core/modules/multiqc/main.nf index da780800..3dceb162 100644 --- a/modules/nf-core/modules/multiqc/main.nf +++ b/modules/nf-core/modules/multiqc/main.nf @@ -1,21 +1,10 @@ -// Import generic module functions -include { initOptions; saveFiles; getSoftwareName } from './functions' - -params.options = [:] -options = initOptions(params.options) - process MULTIQC { label 'process_medium' - publishDir "${params.outdir}", - mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getSoftwareName(task.process), meta:[:], publish_by_meta:[]) } - conda (params.enable_conda ? "bioconda::multiqc=1.10.1" : null) - if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) { - container "https://depot.galaxyproject.org/singularity/multiqc:1.10.1--py_0" - } else { - container "quay.io/biocontainers/multiqc:1.10.1--py_0" - } + conda (params.enable_conda ? 'bioconda::multiqc=1.11' : null) + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/multiqc:1.11--pyhdfd78af_0' : + 'quay.io/biocontainers/multiqc:1.11--pyhdfd78af_0' }" input: path multiqc_files @@ -24,12 +13,16 @@ process MULTIQC { path "*multiqc_report.html", emit: report path "*_data" , emit: data path "*_plots" , optional:true, emit: plots - path "*.version.txt" , emit: version + path "versions.yml" , emit: versions script: - def software = getSoftwareName(task.process) + def args = task.ext.args ?: '' """ - multiqc -f $options.args . - multiqc --version | sed -e "s/multiqc, version //g" > ${software}.version.txt + multiqc -f $args . + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) + END_VERSIONS """ } diff --git a/modules/nf-core/modules/multiqc/meta.yml b/modules/nf-core/modules/multiqc/meta.yml index 532a8bb1..63c75a45 100644 --- a/modules/nf-core/modules/multiqc/meta.yml +++ b/modules/nf-core/modules/multiqc/meta.yml @@ -11,6 +11,7 @@ tools: It's a general use tool, perfect for summarising the output from numerous bioinformatics tools. homepage: https://multiqc.info/ documentation: https://multiqc.info/docs/ + licence: ['GPL-3.0-or-later'] input: - multiqc_files: type: file @@ -29,10 +30,10 @@ output: type: file description: Plots created by MultiQC pattern: "*_data" - - version: + - versions: type: file - description: File containing software version - pattern: "*.{version.txt}" + description: File containing software versions + pattern: "versions.yml" authors: - "@abhi18av" - "@bunop" diff --git a/nextflow.config b/nextflow.config index 77c6b3b3..780eb51e 100644 --- a/nextflow.config +++ b/nextflow.config @@ -26,7 +26,6 @@ params { // Boilerplate options outdir = './results' tracedir = "${params.outdir}/pipeline_info" - publish_dir_mode = 'copy' email = null email_on_fail = null plaintext_email = false @@ -34,14 +33,12 @@ params { help = false validate_params = true show_hidden_params = false - schema_ignore_params = 'genomes,modules' + schema_ignore_params = 'genomes' enable_conda = false - singularity_pull_docker_container = false // Config options custom_config_version = 'master' custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" - hostnames = [:] config_profile_description = null config_profile_contact = null config_profile_url = null @@ -58,9 +55,6 @@ params { // Load base.config by default for all pipelines includeConfig 'conf/base.config' -// Load modules.config for DSL2 module specific options -includeConfig 'conf/modules.config' - // Load nf-core custom profiles from different Institutions try { includeConfig "${params.custom_config_base}/nfcore_custom.config" @@ -68,13 +62,6 @@ try { System.err.println("WARNING: Could not load nf-core/config profiles: ${params.custom_config_base}/nfcore_custom.config") } -// Load igenomes.config if required -if (!params.igenomes_ignore) { - includeConfig 'conf/igenomes.config' -} else { - params.genomes = [:] -} - profiles { debug { process.beforeScript = 'echo $HOSTNAME' } conda { @@ -126,11 +113,22 @@ profiles { test_full { includeConfig 'conf/test_full.config' } } +// Load igenomes.config if required +if (!params.igenomes_ignore) { + includeConfig 'conf/igenomes.config' +} else { + params.genomes = [:] +} + // Export these variables to prevent local Python/R libraries from conflicting with those in the container +// The JULIA depot path has been adjusted to a fixed path `/usr/local/share/julia` that needs to be used for packages in the container. +// See https://apeltzer.github.io/post/03-julia-lang-nextflow/ for details on that. Once we have a common agreement on where to keep Julia packages, this is adjustable. + env { PYTHONNOUSERSITE = 1 R_PROFILE_USER = "/.Rprofile" R_ENVIRON_USER = "/.Renviron" + JULIA_DEPOT_PATH = "/usr/local/share/julia" } // Capture exit codes from upstream processes when piping @@ -160,10 +158,13 @@ manifest { homePage = 'https://github.com/nf-core/cutandrun' description = 'Analysis pipeline for CUT&RUN and CUT&TAG experiments that includes sequencing QC, spike-in normalisation, IgG control normalisation, peak calling and downstream peak analysis.' mainScript = 'main.nf' - nextflowVersion = '!>=21.04.0' + nextflowVersion = '!>=21.10.3' version = '1.0.0' } +// Load modules.config for DSL2 module specific options +includeConfig 'conf/modules.config' + // Function to ensure that resource requirements don't go beyond // a maximum limit def check_max(obj, type) { diff --git a/nextflow_schema.json b/nextflow_schema.json index d0ffdcd2..0950afd7 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -104,12 +104,6 @@ "help_text": "If you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not a problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.", "fa_icon": "fas fa-users-cog" }, - "hostnames": { - "type": "string", - "description": "Institutional configs hostname.", - "hidden": true, - "fa_icon": "fas fa-users-cog" - }, "config_profile_name": { "type": "string", "description": "Institutional config name.", @@ -184,22 +178,6 @@ "fa_icon": "fas fa-question-circle", "hidden": true }, - "publish_dir_mode": { - "type": "string", - "default": "copy", - "description": "Method used to save pipeline results to output directory.", - "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", - "fa_icon": "fas fa-copy", - "enum": [ - "symlink", - "rellink", - "link", - "copy", - "copyNoFollow", - "move" - ], - "hidden": true - }, "email_on_fail": { "type": "string", "description": "Email address for completion summary, only when pipeline fails.", @@ -260,13 +238,6 @@ "description": "Run this workflow with Conda. You can also use '-profile conda' instead of providing this parameter.", "hidden": true, "fa_icon": "fas fa-bacon" - }, - "singularity_pull_docker_container": { - "type": "boolean", - "description": "Instead of directly downloading Singularity images for use with Singularity, force the workflow to pull and convert Docker containers instead.", - "hidden": true, - "fa_icon": "fas fa-toolbox", - "help_text": "This may be useful for example if you are unable to directly pull Singularity containers to run the pipeline due to http/https proxy issues." } } } diff --git a/subworkflows/local/input_check.nf b/subworkflows/local/input_check.nf index b664bc8c..cddcbb3c 100644 --- a/subworkflows/local/input_check.nf +++ b/subworkflows/local/input_check.nf @@ -2,9 +2,7 @@ // Check input samplesheet and get read channels // -params.options = [:] - -include { SAMPLESHEET_CHECK } from '../../modules/local/samplesheet_check' addParams( options: params.options ) +include { SAMPLESHEET_CHECK } from '../../modules/local/samplesheet_check' workflow INPUT_CHECK { take: @@ -12,12 +10,14 @@ workflow INPUT_CHECK { main: SAMPLESHEET_CHECK ( samplesheet ) + .csv .splitCsv ( header:true, sep:',' ) .map { create_fastq_channels(it) } .set { reads } emit: - reads // channel: [ val(meta), [ reads ] ] + reads // channel: [ val(meta), [ reads ] ] + versions = SAMPLESHEET_CHECK.out.versions // channel: [ versions.yml ] } // Function to get list of [ meta, [ fastq_1, fastq_2 ] ] diff --git a/workflows/cutandrun.nf b/workflows/cutandrun.nf index 2ab7e7a3..5325569e 100644 --- a/workflows/cutandrun.nf +++ b/workflows/cutandrun.nf @@ -32,18 +32,10 @@ ch_multiqc_custom_config = params.multiqc_config ? Channel.fromPath(params.multi ======================================================================================== */ -// Don't overwrite global params.modules, create a copy instead and use that within the main script. -def modules = params.modules.clone() - -// -// MODULE: Local to the pipeline -// -include { GET_SOFTWARE_VERSIONS } from '../modules/local/get_software_versions' addParams( options: [publish_files : ['tsv':'']] ) - // // SUBWORKFLOW: Consisting of a mix of local and nf-core/modules // -include { INPUT_CHECK } from '../subworkflows/local/input_check' addParams( options: [:] ) +include { INPUT_CHECK } from '../subworkflows/local/input_check' /* ======================================================================================== @@ -51,14 +43,12 @@ include { INPUT_CHECK } from '../subworkflows/local/input_check' addParams( opti ======================================================================================== */ -def multiqc_options = modules['multiqc'] -multiqc_options.args += params.multiqc_title ? Utils.joinModuleArgs(["--title \"$params.multiqc_title\""]) : '' - // // MODULE: Installed directly from nf-core/modules // -include { FASTQC } from '../modules/nf-core/modules/fastqc/main' addParams( options: modules['fastqc'] ) -include { MULTIQC } from '../modules/nf-core/modules/multiqc/main' addParams( options: multiqc_options ) +include { FASTQC } from '../modules/nf-core/modules/fastqc/main' +include { MULTIQC } from '../modules/nf-core/modules/multiqc/main' +include { CUSTOM_DUMPSOFTWAREVERSIONS } from '../modules/nf-core/modules/custom/dumpsoftwareversions/main' /* ======================================================================================== @@ -71,7 +61,7 @@ def multiqc_report = [] workflow CUTANDRUN { - ch_software_versions = Channel.empty() + ch_versions = Channel.empty() // // SUBWORKFLOW: Read in samplesheet, validate and stage input files @@ -79,6 +69,7 @@ workflow CUTANDRUN { INPUT_CHECK ( ch_input ) + ch_versions = ch_versions.mix(INPUT_CHECK.out.versions) // // MODULE: Run FastQC @@ -86,21 +77,10 @@ workflow CUTANDRUN { FASTQC ( INPUT_CHECK.out.reads ) - ch_software_versions = ch_software_versions.mix(FASTQC.out.version.first().ifEmpty(null)) + ch_versions = ch_versions.mix(FASTQC.out.versions.first()) - // - // MODULE: Pipeline reporting - // - ch_software_versions - .map { it -> if (it) [ it.baseName, it ] } - .groupTuple() - .map { it[1][0] } - .flatten() - .collect() - .set { ch_software_versions } - - GET_SOFTWARE_VERSIONS ( - ch_software_versions.map { it }.collect() + CUSTOM_DUMPSOFTWAREVERSIONS ( + ch_versions.unique().collectFile(name: 'collated_versions.yml') ) // @@ -113,14 +93,14 @@ workflow CUTANDRUN { ch_multiqc_files = ch_multiqc_files.mix(Channel.from(ch_multiqc_config)) ch_multiqc_files = ch_multiqc_files.mix(ch_multiqc_custom_config.collect().ifEmpty([])) ch_multiqc_files = ch_multiqc_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) - ch_multiqc_files = ch_multiqc_files.mix(GET_SOFTWARE_VERSIONS.out.yaml.collect()) + ch_multiqc_files = ch_multiqc_files.mix(CUSTOM_DUMPSOFTWAREVERSIONS.out.mqc_yml.collect()) ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip.collect{it[1]}.ifEmpty([])) MULTIQC ( ch_multiqc_files.collect() ) - multiqc_report = MULTIQC.out.report.toList() - ch_software_versions = ch_software_versions.mix(MULTIQC.out.version.ifEmpty(null)) + multiqc_report = MULTIQC.out.report.toList() + ch_versions = ch_versions.mix(MULTIQC.out.versions) } /*