From 19f19e53c0b75265058c74237c63710cd556e5f5 Mon Sep 17 00:00:00 2001 From: Julianus Pfeuffer Date: Mon, 31 May 2021 16:21:29 +0200 Subject: [PATCH 001/266] [FIX] schema validation with empty default str --- nf_core/schema.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nf_core/schema.py b/nf_core/schema.py index 17fa6ed783..b41258d95d 100644 --- a/nf_core/schema.py +++ b/nf_core/schema.py @@ -114,7 +114,8 @@ def sanitise_param_default(self, param): # For everything else, an empty string is an empty string if isinstance(param["default"], str) and param["default"].strip() == "": - return "" + param["default"] = "" + return param # Integers if param["type"] == "integer": From 90e9204560ea9973186078a33cd0ac9ba10c0fe2 Mon Sep 17 00:00:00 2001 From: Julianus Pfeuffer Date: Mon, 31 May 2021 16:32:56 +0200 Subject: [PATCH 002/266] Try adding test --- tests/test_schema.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_schema.py b/tests/test_schema.py index 7d37636a0d..360a062dd2 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -127,6 +127,15 @@ def test_validate_params_pass(self): self.schema_obj.load_schema() self.schema_obj.input_params = {"input": "fubar"} assert self.schema_obj.validate_params() + + def test_validate_params_pass_ext(self): + """Try validating an extended set of parameters against a schema""" + self.schema_obj.schema = { + "properties": {"foo": {"type": "string"}, "bar": {"type": "string", "default": ""}}, + "required": ["foo"] + } + self.schema_obj.params + assert self.schema_obj.validate_params() def test_validate_params_fail(self): """Check that False is returned if params don't validate against a schema""" From 4f15fe2852abebbd0e36f49df1f99f86ad36db8b Mon Sep 17 00:00:00 2001 From: Julianus Pfeuffer Date: Mon, 31 May 2021 16:44:12 +0200 Subject: [PATCH 003/266] remove leftover line --- tests/test_schema.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 360a062dd2..b20f54efc3 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -134,7 +134,6 @@ def test_validate_params_pass_ext(self): "properties": {"foo": {"type": "string"}, "bar": {"type": "string", "default": ""}}, "required": ["foo"] } - self.schema_obj.params assert self.schema_obj.validate_params() def test_validate_params_fail(self): From b5af037d1ee82ea0ccece99d138dfcb9a8ae9e10 Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 11 Jun 2021 10:21:14 +0200 Subject: [PATCH 004/266] check for invalid config default params --- nf_core/lint/schema_params.py | 7 ++++++ nf_core/schema.py | 43 +++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/nf_core/lint/schema_params.py b/nf_core/lint/schema_params.py index 20c962c226..5cb0c88665 100644 --- a/nf_core/lint/schema_params.py +++ b/nf_core/lint/schema_params.py @@ -29,6 +29,9 @@ def schema_params(self): # Add schema params found in the config but not the schema added_params = self.schema_obj.add_schema_found_configs() + # Invalid default parameters in nextflow.config + invalid_config_default_params = self.schema_obj.invalid_nextflow_config_default_parameters + if len(removed_params) > 0: for param in removed_params: warned.append("Schema param `{}` not found from nextflow config".format(param)) @@ -40,4 +43,8 @@ def schema_params(self): if len(removed_params) == 0 and len(added_params) == 0: passed.append("Schema matched params returned from nextflow config") + if len(invalid_config_default_params) > 0: + for param in invalid_config_default_params: + warned.append(f"Invalid default value for param `{param}` in nextflow config") + return {"passed": passed, "warned": warned, "failed": failed} diff --git a/nf_core/schema.py b/nf_core/schema.py index 17fa6ed783..1b58cc0beb 100644 --- a/nf_core/schema.py +++ b/nf_core/schema.py @@ -37,6 +37,7 @@ def __init__(self): self.schema_params = [] self.input_params = {} self.pipeline_params = {} + self.invalid_nextflow_config_default_parameters = [] self.pipeline_manifest = {} self.schema_from_scratch = False self.no_prompts = False @@ -223,6 +224,48 @@ def validate_default_params(self): raise AssertionError("Default parameters are invalid: {}".format(e.message)) log.info("[green][✓] Default parameters look valid") + # Make sure every default parameter exists in the nextflow.config and is of correc type + if self.pipeline_manifest == {}: + self.get_wf_params() + + # Go over group keys + for group_key, group in schema_no_required["definitions"].items(): + group_properties = group.get("properties") + for param in group_properties: + if param in self.pipeline_params: + self.validate_config_default_parameter(param, group_properties[param]["type"], self.pipeline_params[param]) + + # Go over ungrouped params + ungrouped_properties = self.schema.get("properties") + for param in ungrouped_properties: + if param in self.pipeline_params: + self.validate_config_default_parameter(param, ungrouped_properties[param]["type"], self.pipeline_params[param]) + else: + self.invalid_nextflow_config_default_parameters.append(param) + + def validate_config_default_parameter(self, param, schema_default_type, config_default): + """ + Assure that default parameters in the nextflow.config are correctly set + by comparing them to their type in the schema + """ + if schema_default_type == "string": + if config_default in ["false", "true", "''"]: + self.invalid_nextflow_config_default_parameters.append(param) + if schema_default_type == "boolean": + if not config_default in ["false", "true"]: + self.invalid_nextflow_config_default_parameters.append(param) + if schema_default_type == "integer": + try: + int(config_default) + except ValueError: + self.invalid_nextflow_config_default_parameters.append(param) + if schema_default_type == "number": + try: + float(config_default) + except ValueError: + self.invalid_nextflow_config_default_parameters.append(param) + + def validate_schema(self, schema=None): """ Check that the Schema is valid From f6633f7f312b3ce071c2ffae730aec1d59dd5d27 Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 11 Jun 2021 13:46:15 +0200 Subject: [PATCH 005/266] add ignore parameters --- nf_core/lint/schema_params.py | 2 +- nf_core/schema.py | 38 +++++++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/nf_core/lint/schema_params.py b/nf_core/lint/schema_params.py index 5cb0c88665..6ec34f6adb 100644 --- a/nf_core/lint/schema_params.py +++ b/nf_core/lint/schema_params.py @@ -45,6 +45,6 @@ def schema_params(self): if len(invalid_config_default_params) > 0: for param in invalid_config_default_params: - warned.append(f"Invalid default value for param `{param}` in nextflow config") + warned.append(f"Default value for param `{param}` invalid or missing in nextflow config") return {"passed": passed, "warned": warned, "failed": failed} diff --git a/nf_core/schema.py b/nf_core/schema.py index 1b58cc0beb..65771b33b3 100644 --- a/nf_core/schema.py +++ b/nf_core/schema.py @@ -17,6 +17,7 @@ import webbrowser import yaml import copy +import re import nf_core.list, nf_core.utils @@ -227,27 +228,52 @@ def validate_default_params(self): # Make sure every default parameter exists in the nextflow.config and is of correc type if self.pipeline_manifest == {}: self.get_wf_params() + + # Collect parameters to ignore + if "schema_ignore_params" in self.pipeline_params: + params_ignore = self.pipeline_params.get("schema_ignore_params", "").strip("\"'").split(",") + else: + params_ignore = [] + + # Scrape main.nf for additional parameter declarations and ignore them, too + try: + main_nf = os.path.join(self.pipeline_dir, "main.nf") + with open(main_nf, "r") as fh: + for l in fh: + match = re.match(r"^\s*(params\.[a-zA-Z0-9_]+)\s*=", l) + if match: + params_ignore.append(match.group(1).split(".")[1]) + except FileNotFoundError as e: + log.debug("Could not open {} to look for parameter declarations - {}".format(main_nf, e)) # Go over group keys for group_key, group in schema_no_required["definitions"].items(): group_properties = group.get("properties") for param in group_properties: + if param in params_ignore: + continue if param in self.pipeline_params: self.validate_config_default_parameter(param, group_properties[param]["type"], self.pipeline_params[param]) - # Go over ungrouped params + # Go over ungrouped params if any exist ungrouped_properties = self.schema.get("properties") - for param in ungrouped_properties: - if param in self.pipeline_params: - self.validate_config_default_parameter(param, ungrouped_properties[param]["type"], self.pipeline_params[param]) - else: - self.invalid_nextflow_config_default_parameters.append(param) + if ungrouped_properties: + for param in ungrouped_properties: + if param in params_ignore: + continue + if param in self.pipeline_params: + self.validate_config_default_parameter(param, ungrouped_properties[param]["type"], self.pipeline_params[param]) + else: + self.invalid_nextflow_config_default_parameters.append(param) def validate_config_default_parameter(self, param, schema_default_type, config_default): """ Assure that default parameters in the nextflow.config are correctly set by comparing them to their type in the schema """ + # if default is null, we're good + if config_default == "null": + return if schema_default_type == "string": if config_default in ["false", "true", "''"]: self.invalid_nextflow_config_default_parameters.append(param) From ba5af90a1ccccb1e8bd8e48a25abf3b813b74ef6 Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 11 Jun 2021 13:52:50 +0200 Subject: [PATCH 006/266] comments and changelog --- CHANGELOG.md | 1 + nf_core/schema.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12780b3faf..020c67564a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * Regular release sync fix - this time it was to do with JSON serialisation [[#1072](https://github.com/nf-core/tools/pull/1072)] * Fixed bug in schema validation that ignores upper/lower-case typos in parameters [[#1087](https://github.com/nf-core/tools/issues/1087)] * Bugfix: Download should use path relative to workflow for configs +* Added lint check for valid default parameterse in `nextflow.config` [[#992](https://github.com/nf-core/tools/issues/992)] ### Modules diff --git a/nf_core/schema.py b/nf_core/schema.py index 65771b33b3..12dd91f488 100644 --- a/nf_core/schema.py +++ b/nf_core/schema.py @@ -208,6 +208,9 @@ def validate_default_params(self): """ Check that all default parameters in the schema are valid Ignores 'required' flag, as required parameters might have no defaults + + Additional check that all parameters have defaults in nextflow.config and that + these are valid and adhere to guidelines """ try: assert self.schema is not None @@ -225,7 +228,7 @@ def validate_default_params(self): raise AssertionError("Default parameters are invalid: {}".format(e.message)) log.info("[green][✓] Default parameters look valid") - # Make sure every default parameter exists in the nextflow.config and is of correc type + # Make sure every default parameter exists in the nextflow.config and is of correct type if self.pipeline_manifest == {}: self.get_wf_params() @@ -235,7 +238,7 @@ def validate_default_params(self): else: params_ignore = [] - # Scrape main.nf for additional parameter declarations and ignore them, too + # Scrape main.nf for additional parameter declarations and add to params_ignore try: main_nf = os.path.join(self.pipeline_dir, "main.nf") with open(main_nf, "r") as fh: @@ -254,6 +257,8 @@ def validate_default_params(self): continue if param in self.pipeline_params: self.validate_config_default_parameter(param, group_properties[param]["type"], self.pipeline_params[param]) + else: + self.invalid_nextflow_config_default_parameters.append(param) # Go over ungrouped params if any exist ungrouped_properties = self.schema.get("properties") @@ -274,6 +279,7 @@ def validate_config_default_parameter(self, param, schema_default_type, config_d # if default is null, we're good if config_default == "null": return + # else check for allowed defaults if schema_default_type == "string": if config_default in ["false", "true", "''"]: self.invalid_nextflow_config_default_parameters.append(param) From 6c20bf8f9cbe136924a9d216052c8693be32966f Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 11 Jun 2021 13:59:23 +0200 Subject: [PATCH 007/266] back in black --- nf_core/schema.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/nf_core/schema.py b/nf_core/schema.py index 12dd91f488..f5d092ce92 100644 --- a/nf_core/schema.py +++ b/nf_core/schema.py @@ -231,7 +231,7 @@ def validate_default_params(self): # Make sure every default parameter exists in the nextflow.config and is of correct type if self.pipeline_manifest == {}: self.get_wf_params() - + # Collect parameters to ignore if "schema_ignore_params" in self.pipeline_params: params_ignore = self.pipeline_params.get("schema_ignore_params", "").strip("\"'").split(",") @@ -256,10 +256,12 @@ def validate_default_params(self): if param in params_ignore: continue if param in self.pipeline_params: - self.validate_config_default_parameter(param, group_properties[param]["type"], self.pipeline_params[param]) + self.validate_config_default_parameter( + param, group_properties[param]["type"], self.pipeline_params[param] + ) else: self.invalid_nextflow_config_default_parameters.append(param) - + # Go over ungrouped params if any exist ungrouped_properties = self.schema.get("properties") if ungrouped_properties: @@ -267,7 +269,9 @@ def validate_default_params(self): if param in params_ignore: continue if param in self.pipeline_params: - self.validate_config_default_parameter(param, ungrouped_properties[param]["type"], self.pipeline_params[param]) + self.validate_config_default_parameter( + param, ungrouped_properties[param]["type"], self.pipeline_params[param] + ) else: self.invalid_nextflow_config_default_parameters.append(param) @@ -297,7 +301,6 @@ def validate_config_default_parameter(self, param, schema_default_type, config_d except ValueError: self.invalid_nextflow_config_default_parameters.append(param) - def validate_schema(self, schema=None): """ Check that the Schema is valid From f29b7b6e9b0ed8baa2deb730005ae1469ba05a92 Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 11 Jun 2021 15:07:36 +0200 Subject: [PATCH 008/266] use null instead of false --- nf_core/schema.py | 13 +------------ nf_core/utils.py | 2 +- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/nf_core/schema.py b/nf_core/schema.py index f5d092ce92..4c9f4a8401 100644 --- a/nf_core/schema.py +++ b/nf_core/schema.py @@ -229,7 +229,7 @@ def validate_default_params(self): log.info("[green][✓] Default parameters look valid") # Make sure every default parameter exists in the nextflow.config and is of correct type - if self.pipeline_manifest == {}: + if self.pipeline_params == {}: self.get_wf_params() # Collect parameters to ignore @@ -238,17 +238,6 @@ def validate_default_params(self): else: params_ignore = [] - # Scrape main.nf for additional parameter declarations and add to params_ignore - try: - main_nf = os.path.join(self.pipeline_dir, "main.nf") - with open(main_nf, "r") as fh: - for l in fh: - match = re.match(r"^\s*(params\.[a-zA-Z0-9_]+)\s*=", l) - if match: - params_ignore.append(match.group(1).split(".")[1]) - except FileNotFoundError as e: - log.debug("Could not open {} to look for parameter declarations - {}".format(main_nf, e)) - # Go over group keys for group_key, group in schema_no_required["definitions"].items(): group_properties = group.get("properties") diff --git a/nf_core/utils.py b/nf_core/utils.py index 7d44a1850f..c335a06aa2 100644 --- a/nf_core/utils.py +++ b/nf_core/utils.py @@ -250,7 +250,7 @@ def fetch_wf_config(wf_path): for l in fh: match = re.match(r"^\s*(params\.[a-zA-Z0-9_]+)\s*=", l) if match: - config[match.group(1)] = "false" + config[match.group(1)] = "null" except FileNotFoundError as e: log.debug("Could not open {} to look for parameter declarations - {}".format(main_nf, e)) From 31fcebd9352e8e4337bb8fd20bd074ea389a33d5 Mon Sep 17 00:00:00 2001 From: Kevin Menden Date: Mon, 5 Jul 2021 11:15:32 +0200 Subject: [PATCH 009/266] Update CHANGELOG.md Co-authored-by: Erik Danielsson <53212377+ErikDanielsson@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 020c67564a..17198c5fbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ * Regular release sync fix - this time it was to do with JSON serialisation [[#1072](https://github.com/nf-core/tools/pull/1072)] * Fixed bug in schema validation that ignores upper/lower-case typos in parameters [[#1087](https://github.com/nf-core/tools/issues/1087)] * Bugfix: Download should use path relative to workflow for configs -* Added lint check for valid default parameterse in `nextflow.config` [[#992](https://github.com/nf-core/tools/issues/992)] +* Added lint check for valid default parameters in `nextflow.config` [[#992](https://github.com/nf-core/tools/issues/992)] ### Modules From cd103d613168f5cd82a8ed7fbedddba42741202e Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 6 Jul 2021 17:14:25 +0200 Subject: [PATCH 010/266] Converted tools issue templates into new yml form syntax --- .github/ISSUE_TEMPLATE/bug_report.md | 45 ---------------------- .github/ISSUE_TEMPLATE/bug_report.yml | 44 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 1 - .github/ISSUE_TEMPLATE/feature_request.md | 32 --------------- .github/ISSUE_TEMPLATE/feature_request.yml | 11 ++++++ 5 files changed, 55 insertions(+), 78 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 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 8f2a1bccff..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -name: Bug report -about: Report something that is broken or incorrect -labels: bug ---- - - - -## Description of the bug - - - -## Steps to reproduce - -Steps to reproduce the behaviour: - -1. Command line: -2. See error: - -## Expected behaviour - - - -## System - -- Hardware: -- Executor: -- OS: -- Version of nf-core/tools: -- Python version: - -## Nextflow Installation - -- Version: - -## Additional context - - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..e2aa44a764 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,44 @@ +name: Bug report +description: Report something that is broken or incorrect +labels: bug +body: + + - type: markdown + attributes: + value: | + Hi there! + + Thanks for telling us about a problem with the nf-core/tools package. + + - 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 and output from your terminal. + render: console + placeholder: | + $ nf-core lint ... + + Some output where something broke + + - type: textarea + id: system + attributes: + label: System information + description: | + A clear and concise description of what the bug is. + + * Nextflow version _(eg. 21.04.01)_ + * Hardware _(eg. HPC, Desktop, Cloud)_ + * Executor _(eg. slurm, local, awsbatch)_ + * OS _(eg. CentOS Linux, macOS, Linux Mint)_ + * Version of nf-core/tools _(eg. 1.1, 1.5, 1.8.2)_ + * Python version _(eg. 3.7, 3.8)_ diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 2a03179137..a6a4b51932 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 8c4e9237fe..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for the nf-core website -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 0000000000..5d4d7b17a2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,11 @@ +name: Feature request +description: Suggest an idea for nf-core/tools +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 From 174dffa5782122a590e31623b331bfb666d50b1f Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 6 Jul 2021 19:52:32 +0200 Subject: [PATCH 011/266] Use issue template forms for the pipeline template too --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 - .../.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 ---------- .../ISSUE_TEMPLATE/feature_request.yml | 11 ++++ 6 files changed, 63 insertions(+), 98 deletions(-) delete mode 100644 nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 nf_core/pipeline-template/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 nf_core/pipeline-template/.github/ISSUE_TEMPLATE/feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e2aa44a764..1046a3fa20 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -34,8 +34,6 @@ body: attributes: label: System information description: | - A clear and concise description of what the bug is. - * Nextflow version _(eg. 21.04.01)_ * Hardware _(eg. HPC, Desktop, Cloud)_ * Executor _(eg. slurm, local, awsbatch)_ diff --git a/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.md b/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 5c5100c97b..0000000000 --- a/nf_core/pipeline-template/.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) -- [ ] [{{ name }} pipeline documentation](https://nf-co.re/{{ short_name }}/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/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.yml b/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..9148922314 --- /dev/null +++ b/nf_core/pipeline-template/.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) + - [{{ name }} pipeline documentation](https://nf-co.re/{{ short_name }}/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 upload (drag and drop) and relevant files. Make into a `.zip` file 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.04.01)_ + * 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 {{ name }} _(eg. 1.1, 1.5, 1.8.2)_ diff --git a/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/config.yml b/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/config.yml index a582ac2fb3..19cd8f7c56 100644 --- a/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/config.yml +++ b/nf_core/pipeline-template/.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/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/feature_request.md b/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 1727d53f01..0000000000 --- a/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for the {{ name }} 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/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/feature_request.yml b/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000000..ad3a715de3 --- /dev/null +++ b/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,11 @@ +name: Feature request +description: Suggest an idea for the {{ name }} 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 From 08008d352e4335cac497292acee073dfc49316d2 Mon Sep 17 00:00:00 2001 From: "Maxime U. Garcia" Date: Fri, 16 Jul 2021 17:55:22 +0200 Subject: [PATCH 012/266] Update nextflow.config Load genomes after profiles in case they are changed (ie tests) (sarek stuff) --- nf_core/pipeline-template/nextflow.config | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nf_core/pipeline-template/nextflow.config b/nf_core/pipeline-template/nextflow.config index 3f23f45b34..f275f525d2 100644 --- a/nf_core/pipeline-template/nextflow.config +++ b/nf_core/pipeline-template/nextflow.config @@ -68,13 +68,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,6 +119,13 @@ 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 env { PYTHONNOUSERSITE = 1 From 5376811f1c910227d00bf6a130eac55fc81b07ff Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Tue, 27 Jul 2021 16:39:13 +0100 Subject: [PATCH 013/266] Bump versions to 2.2dev --- CHANGELOG.md | 8 ++++++++ setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c49fa0212e..2ce74ef28f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # nf-core/tools: Changelog +## v2.2dev + +### Template + +### General + +### Modules + ## [v2.1 - Zinc Zebra](https://github.com/nf-core/tools/releases/tag/2.1) - [2021-07-27] ### Template diff --git a/setup.py b/setup.py index 5be47e737a..513f9d9fec 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages -version = "2.1" +version = "2.2dev" with open("README.md") as f: readme = f.read() From 05faa5a6cff4a27cfe72084911718a6927358be4 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Thu, 29 Jul 2021 14:11:16 +0200 Subject: [PATCH 014/266] fix typo --- nf_core/modules/module_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/modules/module_utils.py b/nf_core/modules/module_utils.py index 00d32090ef..30d6e4cc08 100644 --- a/nf_core/modules/module_utils.py +++ b/nf_core/modules/module_utils.py @@ -396,7 +396,7 @@ def prompt_module_version_sha(module, modules_repo, installed_sha=None): log.warning(e) next_page_commits = None - while git_sha is "": + while git_sha == "": commits = next_page_commits try: next_page_commits = get_module_git_log( From e48f024b85bb06b2e73915cd6197c2e93967ad85 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Thu, 29 Jul 2021 14:13:25 +0200 Subject: [PATCH 015/266] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ce74ef28f..1643031624 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Modules +* Fixed typo in `module_utils.py`. + ## [v2.1 - Zinc Zebra](https://github.com/nf-core/tools/releases/tag/2.1) - [2021-07-27] ### Template From a07fba28d32b64c185195dbe98f3bafcf1635a51 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Thu, 29 Jul 2021 14:34:05 +0200 Subject: [PATCH 016/266] Make questionary asks unsafe --- nf_core/modules/bump_versions.py | 2 +- nf_core/modules/create.py | 2 +- nf_core/modules/remove.py | 4 ++-- nf_core/modules/update.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nf_core/modules/bump_versions.py b/nf_core/modules/bump_versions.py index 9189cbb1f6..4d24faa52b 100644 --- a/nf_core/modules/bump_versions.py +++ b/nf_core/modules/bump_versions.py @@ -83,7 +83,7 @@ def bump_versions(self, module=None, all_modules=False, show_uptodate=False): "Tool name:", choices=[m.module_name for m in nfcore_modules], style=nf_core.utils.nfcore_question_style, - ).ask() + ).unsafe_ask() if module: self.show_up_to_date = True diff --git a/nf_core/modules/create.py b/nf_core/modules/create.py index daeb18eaf9..8473f64b8d 100644 --- a/nf_core/modules/create.py +++ b/nf_core/modules/create.py @@ -204,7 +204,7 @@ def create(self): choices=process_label_defaults, style=nf_core.utils.nfcore_question_style, default="process_low", - ).ask() + ).unsafe_ask() if self.has_meta is None: log.info( diff --git a/nf_core/modules/remove.py b/nf_core/modules/remove.py index f657c361a3..996966e7ee 100644 --- a/nf_core/modules/remove.py +++ b/nf_core/modules/remove.py @@ -46,12 +46,12 @@ def remove(self, module): else: repo_name = questionary.autocomplete( "Repo name:", choices=self.module_names.keys(), style=nf_core.utils.nfcore_question_style - ).ask() + ).unsafe_ask() if module is None: module = questionary.autocomplete( "Tool name:", choices=self.module_names[repo_name], style=nf_core.utils.nfcore_question_style - ).ask() + ).unsafe_ask() # Set the remove folder based on the repository name remove_folder = os.path.split(repo_name) diff --git a/nf_core/modules/update.py b/nf_core/modules/update.py index 2bddfec763..d41544f1c0 100644 --- a/nf_core/modules/update.py +++ b/nf_core/modules/update.py @@ -40,7 +40,7 @@ def update(self, module): "Update all modules or a single named module?", choices=choices, style=nf_core.utils.nfcore_question_style, - ).ask() + ).unsafe_ask() == "All modules" ) From b7f69e62fc88f7cf7048475e5b2937d4652aab74 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Thu, 29 Jul 2021 14:38:42 +0200 Subject: [PATCH 017/266] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ce74ef28f..dbc8656791 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### General +* Changed `questionary` `ask()` to `unsafe_ask()` to not catch `KeyboardInterupts` ([#1237](https://github.com/nf-core/tools/issues/1237)) + ### Modules ## [v2.1 - Zinc Zebra](https://github.com/nf-core/tools/releases/tag/2.1) - [2021-07-27] From 05e571dff188deab54d980c168e8107e77744a90 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Mon, 2 Aug 2021 11:28:18 +0200 Subject: [PATCH 018/266] Fixed crashing lint test due when missing process section --- nf_core/modules/lint/__init__.py | 2 +- nf_core/modules/lint/main_nf.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/nf_core/modules/lint/__init__.py b/nf_core/modules/lint/__init__.py index f9de48a304..857e679ad1 100644 --- a/nf_core/modules/lint/__init__.py +++ b/nf_core/modules/lint/__init__.py @@ -321,7 +321,7 @@ def lint_module(self, mod, local=False): if local: self.main_nf(mod) self.passed += [LintResult(mod, *m) for m in mod.passed] - self.warned += [LintResult(mod, *m) for m in mod.warned] + self.warned += [LintResult(mod, *m) for m in (mod.warned + mod.failed)] # Otherwise run all the lint tests else: diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index 018dc99af2..f1b46de054 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -141,6 +141,13 @@ def check_process_section(self, lines): Specifically checks for correct software versions and containers """ + # Check that we have a process section + if len(lines) == 0: + self.failed.append(("process_exist", "Process definition does not exist", self.main_nf)) + return + else: + self.passed.append(("process_exist", "Process definition exists", self.main_nf)) + # Checks that build numbers of bioconda, singularity and docker container are matching build_id = "build" singularity_tag = "singularity" From bcb92f85ef6f893f8fd863fd70e73e1115c30482 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Mon, 2 Aug 2021 11:29:59 +0200 Subject: [PATCH 019/266] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9f518293f..de57c84cb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Modules * Fixed typo in `module_utils.py`. +* Fixed failing lint test when process section was missing from module. Also added the local failing tests to the warned section of the output table. ([#1235](https://github.com/nf-core/tools/issues/1235)) ## [v2.1 - Zinc Zebra](https://github.com/nf-core/tools/releases/tag/2.1) - [2021-07-27] From b933df8beda99edf92868b8d9af7362d83efa7c6 Mon Sep 17 00:00:00 2001 From: "Maxime U. Garcia" Date: Fri, 6 Aug 2021 14:13:10 +0200 Subject: [PATCH 020/266] Update multiqc_config.yaml I think the docs should be the nf-core website and not the GitHub repo. --- nf_core/pipeline-template/assets/multiqc_config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/assets/multiqc_config.yaml b/nf_core/pipeline-template/assets/multiqc_config.yaml index e3f940c2e7..bbc9a14e1c 100644 --- a/nf_core/pipeline-template/assets/multiqc_config.yaml +++ b/nf_core/pipeline-template/assets/multiqc_config.yaml @@ -1,7 +1,7 @@ report_comment: > This report has been generated by the {{ name }} analysis pipeline. For information about how to interpret these results, please see the - documentation. + documentation. report_section_order: software_versions: order: -1000 From 9571df9a03ed61c394cbe581236e8be94a2ddfe5 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Fri, 6 Aug 2021 14:47:19 +0200 Subject: [PATCH 021/266] Add '--diff' flag to 'nf-core modules update' --- nf_core/__main__.py | 9 ++- nf_core/modules/install.py | 6 +- nf_core/modules/modules_command.py | 7 ++- nf_core/modules/update.py | 88 ++++++++++++++++++++++++++---- 4 files changed, 92 insertions(+), 18 deletions(-) diff --git a/nf_core/__main__.py b/nf_core/__main__.py index 483d709780..4d828fd671 100755 --- a/nf_core/__main__.py +++ b/nf_core/__main__.py @@ -449,14 +449,19 @@ def install(ctx, tool, dir, prompt, force, sha): @click.option("-p", "--prompt", is_flag=True, default=False, help="Prompt for the version of the module") @click.option("-s", "--sha", type=str, metavar="", help="Install module at commit SHA") @click.option("-a", "--all", is_flag=True, default=False, help="Update all modules installed in pipeline") -def update(ctx, tool, dir, force, prompt, sha, all): +@click.option( + "-c", "--diff", is_flag=True, default=False, help="Show differences between module versions before updating" +) +def update(ctx, tool, dir, force, prompt, sha, all, diff): """ Update DSL2 modules within a pipeline. Fetches and updates module files from a remote repo e.g. nf-core/modules. """ try: - module_install = nf_core.modules.ModuleUpdate(dir, force=force, prompt=prompt, sha=sha, update_all=all) + module_install = nf_core.modules.ModuleUpdate( + dir, force=force, prompt=prompt, sha=sha, update_all=all, diff=diff + ) module_install.modules_repo = ctx.obj["modules_repo_obj"] exit_status = module_install.update(tool) if not exit_status and all: diff --git a/nf_core/modules/install.py b/nf_core/modules/install.py index b0d96c4311..c5c33cc2a8 100644 --- a/nf_core/modules/install.py +++ b/nf_core/modules/install.py @@ -82,10 +82,10 @@ def install(self, module): current_entry = None # Set the install folder based on the repository name - install_folder = [self.modules_repo.owner, self.modules_repo.repo] + install_folder = [self.dir, "modules", self.modules_repo.owner, self.modules_repo.repo] # Compute the module directory - module_dir = os.path.join(self.dir, "modules", *install_folder, module) + module_dir = os.path.join(*install_folder, module) # Check that the module is not already installed if (current_entry is not None and os.path.exists(module_dir)) and not self.force: @@ -128,7 +128,7 @@ def install(self, module): log.debug(f"Installing module '{module}' at modules hash {version} from {self.modules_repo.name}") # Download module files - if not self.download_module_file(module, version, self.modules_repo, install_folder, module_dir): + if not self.download_module_file(module, version, self.modules_repo, install_folder): return False # Update module.json with newly installed module diff --git a/nf_core/modules/modules_command.py b/nf_core/modules/modules_command.py index e7ba1a5c05..ba8bba9ef1 100644 --- a/nf_core/modules/modules_command.py +++ b/nf_core/modules/modules_command.py @@ -253,19 +253,20 @@ def clear_module_dir(self, module_name, module_dir): log.error("Could not remove module: {}".format(e)) return False - def download_module_file(self, module_name, module_version, modules_repo, install_folder, module_dir): + def download_module_file(self, module_name, module_version, modules_repo, install_folder, dry_run=False): """Downloads the files of a module from the remote repo""" files = modules_repo.get_module_file_urls(module_name, module_version) log.debug("Fetching module files:\n - {}".format("\n - ".join(files.keys()))) for filename, api_url in files.items(): split_filename = filename.split("/") - dl_filename = os.path.join(self.dir, "modules", *install_folder, *split_filename[1:]) + dl_filename = os.path.join(*install_folder, *split_filename[1:]) try: self.modules_repo.download_gh_file(dl_filename, api_url) except (SystemError, LookupError) as e: log.error(e) return False - log.info("Downloaded {} files to {}".format(len(files), module_dir)) + if not dry_run: + log.info("Downloaded {} files to {}".format(len(files), os.path.join(*install_folder, module_name))) return True def load_modules_json(self): diff --git a/nf_core/modules/update.py b/nf_core/modules/update.py index d41544f1c0..8f41b57850 100644 --- a/nf_core/modules/update.py +++ b/nf_core/modules/update.py @@ -1,6 +1,11 @@ import os +import shutil import questionary import logging +import tempfile +import difflib +from rich.console import Console +from rich.syntax import Syntax import nf_core.utils import nf_core.modules.module_utils @@ -13,12 +18,13 @@ class ModuleUpdate(ModuleCommand): - def __init__(self, pipeline_dir, force=False, prompt=False, sha=None, update_all=False): + def __init__(self, pipeline_dir, force=False, prompt=False, sha=None, update_all=False, diff=False): super().__init__(pipeline_dir) self.force = force self.prompt = prompt self.sha = sha self.update_all = update_all + self.diff = diff def update(self, module): if self.repo_type == "modules": @@ -173,6 +179,7 @@ def update(self, module): exit_value = True for modules_repo, module, sha in repos_mods_shas: + dry_run = self.diff if not module_exist_in_repo(module, modules_repo): warn_msg = f"Module '{module}' not found in remote '{modules_repo.name}' ({modules_repo.branch})" if self.update_all: @@ -187,10 +194,10 @@ def update(self, module): current_entry = None # Set the install folder based on the repository name - install_folder = [modules_repo.owner, modules_repo.repo] + install_folder = [self.dir, "modules", modules_repo.owner, modules_repo.repo] # Compute the module directory - module_dir = os.path.join(self.dir, "modules", *install_folder, module) + module_dir = os.path.join(*install_folder, module) if sha: version = sha @@ -225,17 +232,78 @@ def update(self, module): log.info(f"'{modules_repo.name}/{module}' is already up to date") continue - log.info(f"Updating '{modules_repo.name}/{module}'") - log.debug(f"Updating module '{module}' to {version} from {modules_repo.name}") + if not dry_run: + log.info(f"Updating '{modules_repo.name}/{module}'") + log.debug(f"Updating module '{module}' to {version} from {modules_repo.name}") - log.debug(f"Removing old version of module '{module}'") - self.clear_module_dir(module, module_dir) + log.debug(f"Removing old version of module '{module}'") + self.clear_module_dir(module, module_dir) + + if dry_run: + # Set the install folder to a temporary directory + install_folder = ["/tmp", next(tempfile._get_candidate_names())] # Download module files - if not self.download_module_file(module, version, modules_repo, install_folder, module_dir): + if not self.download_module_file(module, version, modules_repo, install_folder, dry_run=dry_run): exit_value = False continue - # Update module.json with newly installed module - self.update_modules_json(modules_json, modules_repo.name, module, version) + if dry_run: + console = Console(force_terminal=nf_core.utils.rich_force_colors()) + files = os.listdir(os.path.join(*install_folder, module)) + temp_folder = os.path.join(*install_folder, module) + log.info( + f"Changes in module '{module}' between ({current_entry['git_sha'] if current_entry is not None else '?'}) and ({version if version is not None else 'latest'})" + ) + + for file in files: + temp_path = os.path.join(temp_folder, file) + curr_path = os.path.join(module_dir, file) + if os.path.exists(temp_path) and os.path.exists(curr_path): + with open(temp_path, "r") as fh: + new_lines = fh.readlines() + with open(curr_path, "r") as fh: + old_lines = fh.readlines() + if new_lines == old_lines: + # The files are identical + log.info(f"'{os.path.join(module, file)}' is unchanged") + else: + log.info(f"Changes in '{os.path.join(module, file)}':") + # Compute the diff + diff = difflib.unified_diff( + old_lines, + new_lines, + fromfile=f"{os.path.join(module, file)} (installed)", + tofile=f"{os.path.join(module, file)} (new)", + ) + + # Pretty print the diff using the pygments diff lexer + console.print(Syntax("".join(diff), "diff", theme="ansi_light")) + + elif os.path.exists(temp_path): + # The file was created between the commits + log.info(f"Created file '{file}'") + + elif os.path.exists(curr_path): + # The file was removed between the commits + log.info(f"Removed file '{file}'") + + # Ask the user if they want to install the module + dry_run = not questionary.confirm("Update module?", default=False).unsafe_ask() + if not dry_run: + # The new module files are already installed + # we just need to clear the directory and move the + # new files from the temporary directory + self.clear_module_dir(module, module_dir) + os.mkdir(module_dir) + for file in files: + path = os.path.join(temp_folder, file) + if os.path.exists(path): + shutil.move(path, os.path.join(module_dir, file)) + log.info(f"Updating '{modules_repo.name}/{module}'") + log.debug(f"Updating module '{module}' to {version} from {modules_repo.name}") + + if not dry_run: + # Update module.json with newly installed module + self.update_modules_json(modules_json, modules_repo.name, module, version) return exit_value From 14dec1ec9ff18fc0ff11e1624d57b15503cbb84b Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Fri, 6 Aug 2021 14:50:27 +0200 Subject: [PATCH 022/266] Update docs and CHANGELOG.md --- CHANGELOG.md | 1 + README.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9f518293f..c757b4ae2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Modules * Fixed typo in `module_utils.py`. +* Added `--diff` flag to `nf-core modules update` which shows the diff between the installed files and the versions ## [v2.1 - Zinc Zebra](https://github.com/nf-core/tools/releases/tag/2.1) - [2021-07-27] diff --git a/README.md b/README.md index 3a8fbc47ab..c874796049 100644 --- a/README.md +++ b/README.md @@ -1019,11 +1019,12 @@ INFO Downloaded 3 files to ./modules/nf-core/modules/fastqc You can pass the module name as an optional argument to `nf-core modules update` instead of using the cli prompt, eg: `nf-core modules update fastqc`. You can specify a pipeline directory other than the current working directory by using the `--dir `. -There are four additional flags that you can use with this command: +There are five additional flags that you can use with this command: * `--force`: Reinstall module even if it appears to be up to date * `--prompt`: Select the module version using a cli prompt. * `--sha `: Install the module at a specific commit from the `nf-core/modules` repository. +* `--diff`: Show the diff between the installed files and the new version before installing. * `--all`: Use this flag to run the command on all modules in the pipeline. If you don't want to update certain modules or want to update them to specific versions, you can make use of the `.nf-core.yml` configuration file. For example, you can prevent the `star/align` module installed from `nf-core/modules` from being updated by adding the following to the `.nf-core.yml` file: From d959a677d78b06567240754f0fc8a5a21d9a4a40 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Fri, 6 Aug 2021 17:23:22 +0200 Subject: [PATCH 023/266] Print to log that module entry was found in '.nf-core.yml' --- nf_core/modules/update.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nf_core/modules/update.py b/nf_core/modules/update.py index 8f41b57850..dc69c5b15a 100644 --- a/nf_core/modules/update.py +++ b/nf_core/modules/update.py @@ -100,12 +100,15 @@ def update(self, module): log.info("Module's update entry in '.nf-core.yml' is set to False") return False elif isinstance(config_entry, str): + sha = config_entry if self.sha: log.warning( - "Found entry in '.nf-core.yml' for module " + f"Found entry in '.nf-core.yml' for module '{module}' " "which will override version specified with '--sha'" ) - sha = config_entry + else: + log.info(f"Found entry in '.nf-core.yml' for module '{module}'") + log.info(f"Updating module to ({sha})") else: log.error("Module's update entry in '.nf-core.yml' is of wrong type") return False From a640d64d82151e842c81ec9e2b559e5eb8699f3b Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Mon, 9 Aug 2021 11:00:11 +0200 Subject: [PATCH 024/266] Fix bug --- nf_core/modules/create.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nf_core/modules/create.py b/nf_core/modules/create.py index 8473f64b8d..b8fc6d30db 100644 --- a/nf_core/modules/create.py +++ b/nf_core/modules/create.py @@ -145,7 +145,9 @@ def create(self): log.info(f"Using Bioconda package: '{self.bioconda}'") break except (ValueError, LookupError) as e: - log.warning(f"Could not find Conda dependency using the Anaconda API: '{self.tool}'") + log.warning( + f"Could not find Conda dependency using the Anaconda API: '{self.tool_conda_name if self.tool_conda_name else self.tool}'" + ) if rich.prompt.Confirm.ask(f"[violet]Do you want to enter a different Bioconda package name?"): self.tool_conda_name = rich.prompt.Prompt.ask("[violet]Name of Bioconda package").strip() continue From 43af5384c24c413b1acee566d3be1db48bee7065 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Mon, 9 Aug 2021 11:14:30 +0200 Subject: [PATCH 025/266] Update help texts --- nf_core/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/__main__.py b/nf_core/__main__.py index 4d828fd671..4c08e4fc87 100755 --- a/nf_core/__main__.py +++ b/nf_core/__main__.py @@ -502,10 +502,10 @@ def create_module(ctx, tool, dir, author, label, meta, no_meta, force, conda_nam """ Create a new DSL2 module from the nf-core template. - If is a pipeline, this function creates a file called + If the working directory is a pipeline, this function creates a file called 'modules/local/tool_subtool.nf' - If is a clone of nf-core/modules, it creates or modifies files + If the working directory is a clone of nf-core/modules, it creates or modifies files in 'modules/', 'tests/modules' and 'tests/config/pytest_modules.yml' """ # Combine two bool flags into one variable From fe480ad8e1b2864015331b1bb2c581d73a52d409 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Mon, 9 Aug 2021 11:16:08 +0200 Subject: [PATCH 026/266] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c757b4ae2b..fb5a6b3465 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * Fixed typo in `module_utils.py`. * Added `--diff` flag to `nf-core modules update` which shows the diff between the installed files and the versions +* Update `nf-core modules create` help texts which were not changed with the introduction of the `--dir` flag ## [v2.1 - Zinc Zebra](https://github.com/nf-core/tools/releases/tag/2.1) - [2021-07-27] From 22a8ff83f14e1e455b831d8b6ada85d7dd472319 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Mon, 9 Aug 2021 11:47:31 +0200 Subject: [PATCH 027/266] Add '-r' option to nextflow command --- nf_core/launch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/launch.py b/nf_core/launch.py index 36a21b076b..3bf257ff07 100644 --- a/nf_core/launch.py +++ b/nf_core/launch.py @@ -209,7 +209,7 @@ def get_pipeline_schema(self): return False self.pipeline_revision = nf_core.utils.prompt_pipeline_release_branch(wf_releases, wf_branches) - self.nextflow_cmd += " -r {}".format(self.pipeline_revision) + self.nextflow_cmd += " -r {}".format(self.pipeline_revision) # Get schema from name, load it and lint it try: From 4a8740a8ad9ec6de3a0fcaf3e5f023205a45a4d9 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Mon, 9 Aug 2021 11:51:30 +0200 Subject: [PATCH 028/266] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c757b4ae2b..e22884f09c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### General * Changed `questionary` `ask()` to `unsafe_ask()` to not catch `KeyboardInterupts` ([#1237](https://github.com/nf-core/tools/issues/1237)) +* Fixed bug in `nf-core launch` due to revisions specified with `-r` not being added to nextflow command. ([#1246](https://github.com/nf-core/tools/issues/1246)) ### Modules From a50d5bd900f48a077d15aec216d393bf76f8fe5a Mon Sep 17 00:00:00 2001 From: Erik Danielsson <53212377+ErikDanielsson@users.noreply.github.com> Date: Mon, 9 Aug 2021 14:30:10 +0200 Subject: [PATCH 029/266] Apply suggestions from code review --- nf_core/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/__main__.py b/nf_core/__main__.py index 4c08e4fc87..24c575d576 100755 --- a/nf_core/__main__.py +++ b/nf_core/__main__.py @@ -502,10 +502,10 @@ def create_module(ctx, tool, dir, author, label, meta, no_meta, force, conda_nam """ Create a new DSL2 module from the nf-core template. - If the working directory is a pipeline, this function creates a file called + If the specified is a pipeline, this function creates a file called 'modules/local/tool_subtool.nf' - If the working directory is a clone of nf-core/modules, it creates or modifies files + If the specified directory is a clone of nf-core/modules, it creates or modifies files in 'modules/', 'tests/modules' and 'tests/config/pytest_modules.yml' """ # Combine two bool flags into one variable From 8765910ba69a19bd62ebc077aa23fc9e42fab84d Mon Sep 17 00:00:00 2001 From: Erik Danielsson <53212377+ErikDanielsson@users.noreply.github.com> Date: Mon, 9 Aug 2021 14:30:43 +0200 Subject: [PATCH 030/266] Update nf_core/__main__.py --- nf_core/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/__main__.py b/nf_core/__main__.py index 24c575d576..a9b5f05b68 100755 --- a/nf_core/__main__.py +++ b/nf_core/__main__.py @@ -502,7 +502,7 @@ def create_module(ctx, tool, dir, author, label, meta, no_meta, force, conda_nam """ Create a new DSL2 module from the nf-core template. - If the specified is a pipeline, this function creates a file called + If the specified directory is a pipeline, this function creates a file called 'modules/local/tool_subtool.nf' If the specified directory is a clone of nf-core/modules, it creates or modifies files From 3759ea04a6a7511520bc2a58f502d9b125bc2154 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Mon, 9 Aug 2021 14:34:24 +0200 Subject: [PATCH 031/266] Update README.md lint regex --- nf_core/lint/readme.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/lint/readme.py b/nf_core/lint/readme.py index 7031bb05fd..f42239a8b5 100644 --- a/nf_core/lint/readme.py +++ b/nf_core/lint/readme.py @@ -63,7 +63,7 @@ def readme(self): # Check that the minimum version mentioned in the quick start section is consistent # Looking for: "1. Install [`Nextflow`](https://nf-co.re/usage/installation) (`>=21.04.0`)" - nf_version_re = r"1\.\s*Install\s*\[`Nextflow`\]\(https://nf-co.re/usage/installation\)\s*\(`>=(\d*\.\d*\.\d*)`\)" + nf_version_re = r"1\.\s*Install\s*\[`Nextflow`\]\(https://www.nextflow.io/docs/latest/getstarted.html#installation\)\s*\(`>=(\d*\.\d*\.\d*)`\)" match = re.search(nf_version_re, content) if match: nf_quickstart_version = match.group(1) From 30efe6ec68cad67a46b6ab256dd2cbcd6a88e8ea Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Mon, 9 Aug 2021 14:38:32 +0200 Subject: [PATCH 032/266] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e22884f09c..29d5bda6e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Changed `questionary` `ask()` to `unsafe_ask()` to not catch `KeyboardInterupts` ([#1237](https://github.com/nf-core/tools/issues/1237)) * Fixed bug in `nf-core launch` due to revisions specified with `-r` not being added to nextflow command. ([#1246](https://github.com/nf-core/tools/issues/1246)) +* Update regex in `readme` test of `nf-core lint` to agree with the pipeline template ([#1260](https://github.com/nf-core/tools/issues/1260)) ### Modules From 4a4d821ef7e44cd9f5e4884d44e442d7bddefd35 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Mon, 9 Aug 2021 15:36:48 +0200 Subject: [PATCH 033/266] Fix outdated fix message in lint --- CHANGELOG.md | 1 + nf_core/lint_utils.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e22884f09c..3c968d89c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Changed `questionary` `ask()` to `unsafe_ask()` to not catch `KeyboardInterupts` ([#1237](https://github.com/nf-core/tools/issues/1237)) * Fixed bug in `nf-core launch` due to revisions specified with `-r` not being added to nextflow command. ([#1246](https://github.com/nf-core/tools/issues/1246)) +* Update 'fix' message in `nf-core lint` to conform to the current command line options. ([#1259](https://github.com/nf-core/tools/issues/1259)) ### Modules diff --git a/nf_core/lint_utils.py b/nf_core/lint_utils.py index 6e2084922e..ea62dd2b00 100644 --- a/nf_core/lint_utils.py +++ b/nf_core/lint_utils.py @@ -38,7 +38,9 @@ def print_fixes(lint_obj, module_lint_obj): """Prints available and applied fixes""" if len(lint_obj.could_fix): - fix_cmd = "nf-core lint {} --fix {}".format(lint_obj.wf_path, " --fix ".join(lint_obj.could_fix)) + fix_cmd = "nf-core lint {}--fix {}".format( + "" if lint_obj.wf_path == "." else f"--dir {lint_obj.wf_path}", " --fix ".join(lint_obj.could_fix) + ) console.print( f"\nTip: Some of these linting errors can automatically be resolved with the following command:\n\n[blue] {fix_cmd}\n" ) From 66c0c4e8a082231af5ad48e357f9e3d7117241b5 Mon Sep 17 00:00:00 2001 From: Erik Danielsson Date: Mon, 9 Aug 2021 15:50:23 +0200 Subject: [PATCH 034/266] Update link in help text --- nf_core/lint/readme.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/lint/readme.py b/nf_core/lint/readme.py index f42239a8b5..ab240fc267 100644 --- a/nf_core/lint/readme.py +++ b/nf_core/lint/readme.py @@ -62,7 +62,7 @@ def readme(self): warned.append("README did not have a Nextflow minimum version badge.") # Check that the minimum version mentioned in the quick start section is consistent - # Looking for: "1. Install [`Nextflow`](https://nf-co.re/usage/installation) (`>=21.04.0`)" + # Looking for: "1. Install [`Nextflow`](https://www.nextflow.io/docs/latest/getstarted.html#installation) (`>=21.04.0`)" nf_version_re = r"1\.\s*Install\s*\[`Nextflow`\]\(https://www.nextflow.io/docs/latest/getstarted.html#installation\)\s*\(`>=(\d*\.\d*\.\d*)`\)" match = re.search(nf_version_re, content) if match: From 1c927e9481702c9c7d48915592a83a5df04cf1bb Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Mon, 2 Aug 2021 16:16:10 +0200 Subject: [PATCH 035/266] Change filter to support multiple version file outputs --- nf_core/pipeline-template/workflows/pipeline.nf | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/nf_core/pipeline-template/workflows/pipeline.nf b/nf_core/pipeline-template/workflows/pipeline.nf index fe1882b420..c27d4b4df3 100644 --- a/nf_core/pipeline-template/workflows/pipeline.nf +++ b/nf_core/pipeline-template/workflows/pipeline.nf @@ -92,15 +92,12 @@ workflow {{ short_name|upper }} { // MODULE: Pipeline reporting // ch_software_versions - .map { it -> if (it) [ it.baseName, it ] } - .groupTuple() - .map { it[1][0] } .flatten() - .collect() + .unique { it.getName() + it.getText() } .set { ch_software_versions } GET_SOFTWARE_VERSIONS ( - ch_software_versions.map { it }.collect() + ch_software_versions.collect() ) // From e147cc931d004ef7dbd4870bfe63464255e4a952 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Mon, 2 Aug 2021 22:18:29 +0200 Subject: [PATCH 036/266] Add collectFile to combine different versions --- nf_core/pipeline-template/workflows/pipeline.nf | 1 + 1 file changed, 1 insertion(+) diff --git a/nf_core/pipeline-template/workflows/pipeline.nf b/nf_core/pipeline-template/workflows/pipeline.nf index c27d4b4df3..880886fd64 100644 --- a/nf_core/pipeline-template/workflows/pipeline.nf +++ b/nf_core/pipeline-template/workflows/pipeline.nf @@ -94,6 +94,7 @@ workflow {{ short_name|upper }} { ch_software_versions .flatten() .unique { it.getName() + it.getText() } + .collectFile(sort:true) { it -> [ it.getName(), it.getText()] } .set { ch_software_versions } GET_SOFTWARE_VERSIONS ( From 2942f8384d9420ad5a6aa803aaf6968ccdc5f2b9 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Mon, 2 Aug 2021 22:27:29 +0200 Subject: [PATCH 037/266] Change scrape versions to comma separate multiple versions --- nf_core/pipeline-template/bin/scrape_software_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/bin/scrape_software_versions.py b/nf_core/pipeline-template/bin/scrape_software_versions.py index 241dc8b7a6..057c99ffd3 100755 --- a/nf_core/pipeline-template/bin/scrape_software_versions.py +++ b/nf_core/pipeline-template/bin/scrape_software_versions.py @@ -11,7 +11,7 @@ software = "{{ name }}" with open(version_file) as fin: - version = fin.read().strip() + version = fin.read().strip().replace('\n',', ') results[software] = version # Dump to YAML From 826661037e95706df18280a2d883bc33ac12c7b6 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Mon, 2 Aug 2021 22:37:15 +0200 Subject: [PATCH 038/266] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe87bf81dc..dc696843c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Template +* Modify software version channel handling to support multiple software version emissions (e.g. from mulled containers), and multiple software versions. + ### General * Changed `questionary` `ask()` to `unsafe_ask()` to not catch `KeyboardInterupts` ([#1237](https://github.com/nf-core/tools/issues/1237)) From deaf0c12c92215c0051831adac39b4dbbcc8944e Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Mon, 2 Aug 2021 22:47:57 +0200 Subject: [PATCH 039/266] black lint update --- nf_core/pipeline-template/bin/scrape_software_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/bin/scrape_software_versions.py b/nf_core/pipeline-template/bin/scrape_software_versions.py index 057c99ffd3..a554188966 100755 --- a/nf_core/pipeline-template/bin/scrape_software_versions.py +++ b/nf_core/pipeline-template/bin/scrape_software_versions.py @@ -11,7 +11,7 @@ software = "{{ name }}" with open(version_file) as fin: - version = fin.read().strip().replace('\n',', ') + version = fin.read().strip().replace("\n", ", ") results[software] = version # Dump to YAML From e3c5fe0873612527af76b56e831daf0968a5fdc5 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Tue, 3 Aug 2021 15:07:41 +0200 Subject: [PATCH 040/266] Software versions as YAML output --- nf_core/module-template/modules/main.nf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 6e4dcde636..c568852139 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -78,6 +78,9 @@ process {{ tool_name_underscore|upper }} { {%- endif %} $bam - echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//' > ${software}.version.txt + cat <<-END_VERSIONS > versions.yml + ${task.process}: + - samtools: \$( samtools --version 2>&1 | sed 's/^.*samtools //; s/Using.*\$// ) + END_VERSIONS """ } From 7a894d668b4429829fea4acd6f8acf1fe50994cd Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Tue, 3 Aug 2021 15:23:38 +0200 Subject: [PATCH 041/266] Add comment to add version for each tool --- nf_core/module-template/modules/main.nf | 1 + 1 file changed, 1 insertion(+) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index c568852139..741f8b04d0 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -62,6 +62,7 @@ process {{ tool_name_underscore|upper }} { // TODO nf-core: Where possible, a command MUST be provided to obtain the version number of the software e.g. 1.10 // If the software is unable to output a version number on the command-line then it can be manually specified // e.g. https://github.com/nf-core/modules/blob/master/software/homer/annotatepeaks/main.nf + // Each tool MUST provide the tool name and version number in the YAML version file (versions.yml) // TODO nf-core: It MUST be possible to pass additional parameters to the tool as a command-line string via the "$options.args" variable // TODO nf-core: If the tool supports multi-threading then you MUST provide the appropriate parameter // using the Nextflow "task" variable e.g. "--threads $task.cpus" From d9267e901bc06ce3d46c3b55780a420dcbea0d89 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Tue, 3 Aug 2021 15:23:57 +0200 Subject: [PATCH 042/266] Update output --- nf_core/module-template/modules/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 741f8b04d0..ee3293f445 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -52,7 +52,7 @@ process {{ tool_name_underscore|upper }} { // TODO nf-core: Named file extensions MUST be emitted for ALL output channels {{ 'tuple val(meta), path("*.bam")' if has_meta else 'path "*.bam"' }}, emit: bam // TODO nf-core: List additional required output channels/values here - path "*.version.txt" , emit: version + path "versions.yml" , emit: version script: def software = getSoftwareName(task.process) From 71d20e1c2ea7e1743931c1c26891ce5595087697 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Tue, 3 Aug 2021 15:27:19 +0200 Subject: [PATCH 043/266] Collect YAML files together --- nf_core/pipeline-template/workflows/pipeline.nf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nf_core/pipeline-template/workflows/pipeline.nf b/nf_core/pipeline-template/workflows/pipeline.nf index 880886fd64..bbd9ae62a4 100644 --- a/nf_core/pipeline-template/workflows/pipeline.nf +++ b/nf_core/pipeline-template/workflows/pipeline.nf @@ -91,14 +91,14 @@ workflow {{ short_name|upper }} { // // MODULE: Pipeline reporting // - ch_software_versions - .flatten() - .unique { it.getName() + it.getText() } - .collectFile(sort:true) { it -> [ it.getName(), it.getText()] } - .set { ch_software_versions } + // ch_software_versions + // .flatten() + // .unique { it.getName() + it.getText() } + // .collectFile(sort:true) { it -> [ it.getName(), it.getText()] } + // .set { ch_software_versions } GET_SOFTWARE_VERSIONS ( - ch_software_versions.collect() + ch_software_versions.collectFile() ) // From 221e49252509f5aee2e77754be59dd0ff9da62bd Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Tue, 3 Aug 2021 15:59:35 +0200 Subject: [PATCH 044/266] Add workflow data to software versions --- .../modules/local/get_software_versions.nf | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/nf_core/pipeline-template/modules/local/get_software_versions.nf b/nf_core/pipeline-template/modules/local/get_software_versions.nf index 8af8af1735..b902834c50 100644 --- a/nf_core/pipeline-template/modules/local/get_software_versions.nf +++ b/nf_core/pipeline-template/modules/local/get_software_versions.nf @@ -26,8 +26,13 @@ process GET_SOFTWARE_VERSIONS { script: // This script is bundled with the pipeline, in {{ name }}/bin/ """ - echo $workflow.manifest.version > pipeline.version.txt - echo $workflow.nextflow.version > nextflow.version.txt - scrape_software_versions.py &> software_versions_mqc.yaml + # echo $workflow.manifest.version > pipeline.version.txt + # echo $workflow.nextflow.version > nextflow.version.txt + # scrape_software_versions.py &> software_versions_mqc.yaml + cat - $versions <<-END_WORKFLOW_VERSION > software_versions_mqc.yaml + Workflow: + - Nextflow: $workflow.nextflow.version + - $workflow.manifest.name: $workflow.manifest.version + END_WORKFLOW_VERSION """ } From 89d291863412e3dac601d8781c8175ef0de0e7f3 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Tue, 3 Aug 2021 20:17:45 +0200 Subject: [PATCH 045/266] Replace software versions tsv with yml --- .../modules/local/get_software_versions.nf | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/nf_core/pipeline-template/modules/local/get_software_versions.nf b/nf_core/pipeline-template/modules/local/get_software_versions.nf index b902834c50..fc7fcbca37 100644 --- a/nf_core/pipeline-template/modules/local/get_software_versions.nf +++ b/nf_core/pipeline-template/modules/local/get_software_versions.nf @@ -21,18 +21,23 @@ process GET_SOFTWARE_VERSIONS { path versions output: - path "software_versions.tsv" , emit: tsv - path 'software_versions_mqc.yaml', emit: yaml + path "software_versions.yml" , emit: yml + path 'software_versions_mqc.yaml', emit: mqc_yaml script: // This script is bundled with the pipeline, in {{ name }}/bin/ """ - # echo $workflow.manifest.version > pipeline.version.txt - # echo $workflow.nextflow.version > nextflow.version.txt - # scrape_software_versions.py &> software_versions_mqc.yaml - cat - $versions <<-END_WORKFLOW_VERSION > software_versions_mqc.yaml + cat - $versions <<-END_WORKFLOW_VERSION > software_versions.yml Workflow: - Nextflow: $workflow.nextflow.version - $workflow.manifest.name: $workflow.manifest.version END_WORKFLOW_VERSION + cat - <( sed 's/^/ /' software_versions.yml ) <<-END_MQC_YAML > software_versions_mqc.yaml + id: 'software_versions' + section_name: '{{ name }} Software Versions' + section_href: 'https://github.com/{{ name }}' + plot_type: 'table' + description: 'are collected at run time from the software output.' + data: + END_MQC_YAML """ } From 3945ee8901217e49ea9c64fe5ab9c708b8fcf973 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Tue, 3 Aug 2021 20:21:34 +0200 Subject: [PATCH 046/266] Update GET_SOFTWARE_VERSION channel names --- nf_core/pipeline-template/workflows/pipeline.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/pipeline-template/workflows/pipeline.nf b/nf_core/pipeline-template/workflows/pipeline.nf index bbd9ae62a4..9fb4d0d0f5 100644 --- a/nf_core/pipeline-template/workflows/pipeline.nf +++ b/nf_core/pipeline-template/workflows/pipeline.nf @@ -38,7 +38,7 @@ 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':'']] ) +include { GET_SOFTWARE_VERSIONS } from '../modules/local/get_software_versions' addParams( options: [publish_files : ['yml':'']] ) // // SUBWORKFLOW: Consisting of a mix of local and nf-core/modules @@ -111,7 +111,7 @@ workflow {{ short_name|upper }} { 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(GET_SOFTWARE_VERSIONS.out.mqc_yaml.collect()) ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip.collect{it[1]}.ifEmpty([])) MULTIQC ( From e6bc9ff61e2ca735cce2fe20003867b3715ab1b4 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Wed, 4 Aug 2021 10:34:36 +0200 Subject: [PATCH 047/266] Update nf_core/pipeline-template/modules/local/get_software_versions.nf Co-authored-by: Gregor Sturm --- nf_core/pipeline-template/modules/local/get_software_versions.nf | 1 + 1 file changed, 1 insertion(+) diff --git a/nf_core/pipeline-template/modules/local/get_software_versions.nf b/nf_core/pipeline-template/modules/local/get_software_versions.nf index fc7fcbca37..943adcf5cf 100644 --- a/nf_core/pipeline-template/modules/local/get_software_versions.nf +++ b/nf_core/pipeline-template/modules/local/get_software_versions.nf @@ -31,6 +31,7 @@ process GET_SOFTWARE_VERSIONS { - Nextflow: $workflow.nextflow.version - $workflow.manifest.name: $workflow.manifest.version END_WORKFLOW_VERSION + cat - <( sed 's/^/ /' software_versions.yml ) <<-END_MQC_YAML > software_versions_mqc.yaml id: 'software_versions' section_name: '{{ name }} Software Versions' From 85839d32de84461119b6174c98a20e19b23ca554 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Wed, 4 Aug 2021 10:42:47 +0200 Subject: [PATCH 048/266] Move sed for readability --- .../pipeline-template/modules/local/get_software_versions.nf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nf_core/pipeline-template/modules/local/get_software_versions.nf b/nf_core/pipeline-template/modules/local/get_software_versions.nf index 943adcf5cf..0059db317a 100644 --- a/nf_core/pipeline-template/modules/local/get_software_versions.nf +++ b/nf_core/pipeline-template/modules/local/get_software_versions.nf @@ -31,14 +31,15 @@ process GET_SOFTWARE_VERSIONS { - Nextflow: $workflow.nextflow.version - $workflow.manifest.name: $workflow.manifest.version END_WORKFLOW_VERSION - - cat - <( sed 's/^/ /' software_versions.yml ) <<-END_MQC_YAML > software_versions_mqc.yaml + + cat - <<-END_MQC_YAML > software_versions_mqc.yaml id: 'software_versions' section_name: '{{ name }} Software Versions' section_href: 'https://github.com/{{ name }}' plot_type: 'table' description: 'are collected at run time from the software output.' data: + \$( sed 's/^/ /' software_versions.yml ) END_MQC_YAML """ } From e99da1e4377bf7b4abe1c2c0525d5333cde93af1 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Wed, 4 Aug 2021 10:55:53 +0200 Subject: [PATCH 049/266] Update nf_core/module-template/modules/main.nf --- nf_core/module-template/modules/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index ee3293f445..9dfb70d706 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -80,7 +80,7 @@ process {{ tool_name_underscore|upper }} { $bam cat <<-END_VERSIONS > versions.yml - ${task.process}: + ${task.process.tokenize(':')[-1]}: - samtools: \$( samtools --version 2>&1 | sed 's/^.*samtools //; s/Using.*\$// ) END_VERSIONS """ From 5ce54a41011f752066b4f6349928fb61c78e32b5 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Tue, 10 Aug 2021 09:16:45 +0200 Subject: [PATCH 050/266] Remove Dash from YAML --- nf_core/module-template/modules/main.nf | 2 +- .../modules/local/get_software_versions.nf | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 9dfb70d706..97d4eb7ccd 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -81,7 +81,7 @@ process {{ tool_name_underscore|upper }} { cat <<-END_VERSIONS > versions.yml ${task.process.tokenize(':')[-1]}: - - samtools: \$( samtools --version 2>&1 | sed 's/^.*samtools //; s/Using.*\$// ) + samtools: \$( samtools --version 2>&1 | sed 's/^.*samtools //; s/Using.*\$// ) END_VERSIONS """ } diff --git a/nf_core/pipeline-template/modules/local/get_software_versions.nf b/nf_core/pipeline-template/modules/local/get_software_versions.nf index 0059db317a..acfec1f288 100644 --- a/nf_core/pipeline-template/modules/local/get_software_versions.nf +++ b/nf_core/pipeline-template/modules/local/get_software_versions.nf @@ -28,8 +28,8 @@ process GET_SOFTWARE_VERSIONS { """ cat - $versions <<-END_WORKFLOW_VERSION > software_versions.yml Workflow: - - Nextflow: $workflow.nextflow.version - - $workflow.manifest.name: $workflow.manifest.version + Nextflow: $workflow.nextflow.version + $workflow.manifest.name: $workflow.manifest.version END_WORKFLOW_VERSION cat - <<-END_MQC_YAML > software_versions_mqc.yaml @@ -39,7 +39,7 @@ process GET_SOFTWARE_VERSIONS { plot_type: 'table' description: 'are collected at run time from the software output.' data: - \$( sed 's/^/ /' software_versions.yml ) + \$( sed 's/^/ /' software_versions.yml ) END_MQC_YAML """ } From fd93ebbc7000a7e54ddac09ca240a5a5e70c3e41 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 10:25:40 +0200 Subject: [PATCH 051/266] Port changes from prototype https://github.com/nf-core/rnaseq/pull/689 Co-Authored-By: Gregor Sturm --- nf_core/module-template/modules/functions.nf | 7 ++ nf_core/module-template/modules/main.nf | 8 +- .../bin/scrape_software_versions.py | 36 -------- .../modules/local/functions.nf | 7 ++ .../modules/local/get_software_versions.nf | 88 +++++++++++++++---- .../nf-core/modules/fastqc/functions.nf | 7 ++ .../modules/nf-core/modules/fastqc/main.nf | 14 ++- .../nf-core/modules/multiqc/functions.nf | 7 ++ .../modules/nf-core/modules/multiqc/main.nf | 8 +- .../pipeline-template/workflows/pipeline.nf | 9 -- 10 files changed, 118 insertions(+), 73 deletions(-) delete mode 100755 nf_core/pipeline-template/bin/scrape_software_versions.py diff --git a/nf_core/module-template/modules/functions.nf b/nf_core/module-template/modules/functions.nf index da9da093d3..4860a36278 100644 --- a/nf_core/module-template/modules/functions.nf +++ b/nf_core/module-template/modules/functions.nf @@ -9,6 +9,13 @@ def getSoftwareName(task_process) { return task_process.tokenize(':')[-1].tokenize('_')[0].toLowerCase() } +// +// Extract name of module from process name using $task.process +// +def getProcessName(task_process) { + return task_process.tokenize(':')[-1] +} + // // Function to initialise default values and to generate a Groovy Map of available options for nf-core modules // diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 97d4eb7ccd..164329e965 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -1,5 +1,5 @@ // Import generic module functions -include { initOptions; saveFiles; getSoftwareName } from './functions' +include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' // TODO nf-core: If in doubt look at other nf-core/modules to see how we are doing things! :) // https://github.com/nf-core/modules/tree/master/software @@ -62,7 +62,7 @@ process {{ tool_name_underscore|upper }} { // TODO nf-core: Where possible, a command MUST be provided to obtain the version number of the software e.g. 1.10 // If the software is unable to output a version number on the command-line then it can be manually specified // e.g. https://github.com/nf-core/modules/blob/master/software/homer/annotatepeaks/main.nf - // Each tool MUST provide the tool name and version number in the YAML version file (versions.yml) + // Each software used MUST provide the software name and version number in the YAML version file (versions.yml) // TODO nf-core: It MUST be possible to pass additional parameters to the tool as a command-line string via the "$options.args" variable // TODO nf-core: If the tool supports multi-threading then you MUST provide the appropriate parameter // using the Nextflow "task" variable e.g. "--threads $task.cpus" @@ -80,8 +80,8 @@ process {{ tool_name_underscore|upper }} { $bam cat <<-END_VERSIONS > versions.yml - ${task.process.tokenize(':')[-1]}: - samtools: \$( samtools --version 2>&1 | sed 's/^.*samtools //; s/Using.*\$// ) + ${getProcessName(task.process)}: + $software: \$( samtools --version 2>&1 | sed 's/^.*samtools //; s/Using.*\$// ) END_VERSIONS """ } diff --git a/nf_core/pipeline-template/bin/scrape_software_versions.py b/nf_core/pipeline-template/bin/scrape_software_versions.py deleted file mode 100755 index a554188966..0000000000 --- a/nf_core/pipeline-template/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 = "{{ name }}" - - with open(version_file) as fin: - version = fin.read().strip().replace("\n", ", ") - results[software] = version - -# Dump to YAML -print( - """ -id: 'software_versions' -section_name: '{{ name }} Software Versions' -section_href: 'https://github.com/{{ name }}' -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/nf_core/pipeline-template/modules/local/functions.nf b/nf_core/pipeline-template/modules/local/functions.nf index da9da093d3..4860a36278 100644 --- a/nf_core/pipeline-template/modules/local/functions.nf +++ b/nf_core/pipeline-template/modules/local/functions.nf @@ -9,6 +9,13 @@ def getSoftwareName(task_process) { return task_process.tokenize(':')[-1].tokenize('_')[0].toLowerCase() } +// +// Extract name of module from process name using $task.process +// +def getProcessName(task_process) { + return task_process.tokenize(':')[-1] +} + // // Function to initialise default values and to generate a Groovy Map of available options for nf-core modules // diff --git a/nf_core/pipeline-template/modules/local/get_software_versions.nf b/nf_core/pipeline-template/modules/local/get_software_versions.nf index acfec1f288..0120a80baf 100644 --- a/nf_core/pipeline-template/modules/local/get_software_versions.nf +++ b/nf_core/pipeline-template/modules/local/get_software_versions.nf @@ -8,11 +8,12 @@ process GET_SOFTWARE_VERSIONS { 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) + // This module requires only the PyYAML library, but rather than create a new container on biocontainers, we reuse the multiqc container. + 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/python:3.8.3" + container "https://depot.galaxyproject.org/singularity/multiqc:1.10.1--pyhdfd78af_1" } else { - container "quay.io/biocontainers/python:3.8.3" + container "quay.io/biocontainers/multiqc:1.10.1--pyhdfd78af_1" } cache false @@ -24,22 +25,71 @@ process GET_SOFTWARE_VERSIONS { path "software_versions.yml" , emit: yml path 'software_versions_mqc.yaml', emit: mqc_yaml - script: // This script is bundled with the pipeline, in {{ name }}/bin/ + script: """ - cat - $versions <<-END_WORKFLOW_VERSION > software_versions.yml - Workflow: - Nextflow: $workflow.nextflow.version - $workflow.manifest.name: $workflow.manifest.version - END_WORKFLOW_VERSION - - cat - <<-END_MQC_YAML > software_versions_mqc.yaml - id: 'software_versions' - section_name: '{{ name }} Software Versions' - section_href: 'https://github.com/{{ name }}' - plot_type: 'table' - description: 'are collected at run time from the software output.' - data: - \$( sed 's/^/ /' software_versions.yml ) - END_MQC_YAML + #!/usr/bin/env python + + import yaml + 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) + + with open("$versions") as f: + versions = yaml.safe_load(f) + + versions["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) + } + + with open("software_versions.yml", 'w') as f: + yaml.dump(versions, f, default_flow_style=False) + with open("software_versions_mqc.yml", 'w') as f: + yaml.dump(versions_mqc, f, default_flow_style=False) """ } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf index da9da093d3..4860a36278 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf @@ -9,6 +9,13 @@ def getSoftwareName(task_process) { return task_process.tokenize(':')[-1].tokenize('_')[0].toLowerCase() } +// +// Extract name of module from process name using $task.process +// +def getProcessName(task_process) { + return task_process.tokenize(':')[-1] +} + // // Function to initialise default values and to generate a Groovy Map of available options for nf-core modules // diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index 39c327b261..f90aae1a55 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -1,5 +1,5 @@ // Import generic module functions -include { initOptions; saveFiles; getSoftwareName } from './functions' +include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' params.options = [:] options = initOptions(params.options) @@ -34,14 +34,22 @@ process FASTQC { """ [ ! -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 + + cat <<-END_VERSIONS > versions.yml + ${getProcessName(task.process)}: + $software: \$(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 + + cat <<-END_VERSIONS > versions.yml + ${getProcessName(task.process)}: + $software: \$(fastqc --version | sed -e "s/FastQC v//g") + END_VERSIONS """ } } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf index da9da093d3..4860a36278 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf @@ -9,6 +9,13 @@ def getSoftwareName(task_process) { return task_process.tokenize(':')[-1].tokenize('_')[0].toLowerCase() } +// +// Extract name of module from process name using $task.process +// +def getProcessName(task_process) { + return task_process.tokenize(':')[-1] +} + // // Function to initialise default values and to generate a Groovy Map of available options for nf-core modules // diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index da78080024..17a3097671 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -1,5 +1,5 @@ // Import generic module functions -include { initOptions; saveFiles; getSoftwareName } from './functions' +include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' params.options = [:] options = initOptions(params.options) @@ -30,6 +30,10 @@ process MULTIQC { def software = getSoftwareName(task.process) """ multiqc -f $options.args . - multiqc --version | sed -e "s/multiqc, version //g" > ${software}.version.txt + + cat <<-END_VERSIONS > versions.yml + ${getProcessName(task.process)}: + $software: \$(multiqc --version | sed -e "s/multiqc, version //g") + END_VERSIONS """ } diff --git a/nf_core/pipeline-template/workflows/pipeline.nf b/nf_core/pipeline-template/workflows/pipeline.nf index 9fb4d0d0f5..f99538d4c8 100644 --- a/nf_core/pipeline-template/workflows/pipeline.nf +++ b/nf_core/pipeline-template/workflows/pipeline.nf @@ -88,15 +88,6 @@ workflow {{ short_name|upper }} { ) ch_software_versions = ch_software_versions.mix(FASTQC.out.version.first().ifEmpty(null)) - // - // MODULE: Pipeline reporting - // - // ch_software_versions - // .flatten() - // .unique { it.getName() + it.getText() } - // .collectFile(sort:true) { it -> [ it.getName(), it.getText()] } - // .set { ch_software_versions } - GET_SOFTWARE_VERSIONS ( ch_software_versions.collectFile() ) From 3a10d60617a3958da9dd36a594b6a0387cf833e5 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 10:27:16 +0200 Subject: [PATCH 052/266] Update comment --- .../pipeline-template/modules/local/get_software_versions.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/modules/local/get_software_versions.nf b/nf_core/pipeline-template/modules/local/get_software_versions.nf index 0120a80baf..ede47ed68d 100644 --- a/nf_core/pipeline-template/modules/local/get_software_versions.nf +++ b/nf_core/pipeline-template/modules/local/get_software_versions.nf @@ -8,7 +8,7 @@ process GET_SOFTWARE_VERSIONS { mode: params.publish_dir_mode, saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:'pipeline_info', meta:[:], publish_by_meta:[]) } - // This module requires only the PyYAML library, but rather than create a new container on biocontainers, we reuse the multiqc container. + // This module only requires the PyYAML library, but rather than create a new container on biocontainers we reuse the multiqc container. 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--pyhdfd78af_1" From 1a7993d28a2cb14449aeadf10436085199c4f79f Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 10:30:27 +0200 Subject: [PATCH 053/266] Update process output --- .../pipeline-template/modules/nf-core/modules/fastqc/main.nf | 2 +- .../pipeline-template/modules/nf-core/modules/multiqc/main.nf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index f90aae1a55..97183b62c9 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -24,7 +24,7 @@ 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: version script: // Add soft-links to original FastQs for consistent naming in pipeline diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index 17a3097671..77c8f4361a 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -24,7 +24,7 @@ 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: version script: def software = getSoftwareName(task.process) From d18174d69a52aa5b56037a9d33e8a5e2ff314869 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 10:40:59 +0200 Subject: [PATCH 054/266] Add getProcessName to modules functions_nf lint --- nf_core/modules/lint/functions_nf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/modules/lint/functions_nf.py b/nf_core/modules/lint/functions_nf.py index 600a1ae7fd..aef0d115ea 100644 --- a/nf_core/modules/lint/functions_nf.py +++ b/nf_core/modules/lint/functions_nf.py @@ -22,7 +22,7 @@ def functions_nf(module_lint_object, module): return # Test whether all required functions are present - required_functions = ["getSoftwareName", "initOptions", "getPathFromList", "saveFiles"] + required_functions = ["getSoftwareName", "getProcessName", "initOptions", "getPathFromList", "saveFiles"] lines = "\n".join(lines) contains_all_functions = True for f in required_functions: From edae68b5b147712462ea4826523c86b2451178cc Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 10:44:50 +0200 Subject: [PATCH 055/266] Update file_exists lint to remove scrape_versions.py --- nf_core/lint/files_exist.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nf_core/lint/files_exist.py b/nf_core/lint/files_exist.py index 7b97aa3fb5..e1b28bc9a3 100644 --- a/nf_core/lint/files_exist.py +++ b/nf_core/lint/files_exist.py @@ -36,7 +36,6 @@ def files_exist(self): assets/email_template.txt assets/nf-core-PIPELINE_logo.png assets/sendmail_template.txt - bin/scrape_software_versions.py conf/modules.config conf/test.config conf/test_full.config @@ -121,7 +120,6 @@ def files_exist(self): [os.path.join("assets", "email_template.txt")], [os.path.join("assets", "sendmail_template.txt")], [os.path.join("assets", f"nf-core-{short_name}_logo.png")], - [os.path.join("bin", "scrape_software_versions.py")], [os.path.join("conf", "modules.config")], [os.path.join("conf", "test.config")], [os.path.join("conf", "test_full.config")], From 8ae12e3d6a2e7893ea6b137697023245764904a8 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 10:47:34 +0200 Subject: [PATCH 056/266] Remove scrape software version from files unchanged. --- nf_core/lint/files_unchanged.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nf_core/lint/files_unchanged.py b/nf_core/lint/files_unchanged.py index 37728b8b06..262e8c4449 100644 --- a/nf_core/lint/files_unchanged.py +++ b/nf_core/lint/files_unchanged.py @@ -92,7 +92,6 @@ def files_unchanged(self): [os.path.join("assets", "email_template.txt")], [os.path.join("assets", "sendmail_template.txt")], [os.path.join("assets", f"nf-core-{short_name}_logo.png")], - [os.path.join("bin", "scrape_software_versions.py")], [os.path.join("docs", "images", f"nf-core-{short_name}_logo.png")], [os.path.join("docs", "README.md")], [os.path.join("lib", "nfcore_external_java_deps.jar")], From 074f1c496c6b2427df25b5d0fddd74b12ccbe1ca Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 10:50:25 +0200 Subject: [PATCH 057/266] Fix YAML output --- .../pipeline-template/modules/local/get_software_versions.nf | 2 +- nf_core/pipeline-template/workflows/pipeline.nf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/pipeline-template/modules/local/get_software_versions.nf b/nf_core/pipeline-template/modules/local/get_software_versions.nf index ede47ed68d..08d58f9c52 100644 --- a/nf_core/pipeline-template/modules/local/get_software_versions.nf +++ b/nf_core/pipeline-template/modules/local/get_software_versions.nf @@ -23,7 +23,7 @@ process GET_SOFTWARE_VERSIONS { output: path "software_versions.yml" , emit: yml - path 'software_versions_mqc.yaml', emit: mqc_yaml + path "software_versions_mqc.yml" , emit: mqc_yml script: """ diff --git a/nf_core/pipeline-template/workflows/pipeline.nf b/nf_core/pipeline-template/workflows/pipeline.nf index f99538d4c8..44924b8116 100644 --- a/nf_core/pipeline-template/workflows/pipeline.nf +++ b/nf_core/pipeline-template/workflows/pipeline.nf @@ -102,7 +102,7 @@ workflow {{ short_name|upper }} { 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.mqc_yaml.collect()) + ch_multiqc_files = ch_multiqc_files.mix(GET_SOFTWARE_VERSIONS.out.mqc_yml.collect()) ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip.collect{it[1]}.ifEmpty([])) MULTIQC ( From c21dabdd26ee8a46d32bcb78387916216e45a2f3 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 11:33:55 +0200 Subject: [PATCH 058/266] Update nf_core/module-template/modules/main.nf Co-authored-by: Gregor Sturm --- nf_core/module-template/modules/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 164329e965..aa2a96021a 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -81,7 +81,7 @@ process {{ tool_name_underscore|upper }} { cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - $software: \$( samtools --version 2>&1 | sed 's/^.*samtools //; s/Using.*\$// ) + samtools: \$( samtools --version 2>&1 | sed 's/^.*samtools //; s/Using.*\$// ) END_VERSIONS """ } From 70d14aaf950c0e825310e1229017b01ccacc6662 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 11:39:43 +0200 Subject: [PATCH 059/266] Remove getSoftwareName function --- nf_core/module-template/modules/functions.nf | 7 ------- nf_core/module-template/modules/main.nf | 3 +-- nf_core/modules/lint/functions_nf.py | 2 +- nf_core/pipeline-template/modules/local/functions.nf | 7 ------- .../modules/nf-core/modules/fastqc/functions.nf | 7 ------- .../modules/nf-core/modules/fastqc/main.nf | 2 +- .../modules/nf-core/modules/multiqc/functions.nf | 7 ------- .../modules/nf-core/modules/multiqc/main.nf | 2 +- 8 files changed, 4 insertions(+), 33 deletions(-) diff --git a/nf_core/module-template/modules/functions.nf b/nf_core/module-template/modules/functions.nf index 4860a36278..0dfaa3a1ca 100644 --- a/nf_core/module-template/modules/functions.nf +++ b/nf_core/module-template/modules/functions.nf @@ -2,13 +2,6 @@ // 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() -} - // // Extract name of module from process name using $task.process // diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index aa2a96021a..b72659f824 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -1,5 +1,5 @@ // Import generic module functions -include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' +include { initOptions; saveFiles; getProcessName } from './functions' // TODO nf-core: If in doubt look at other nf-core/modules to see how we are doing things! :) // https://github.com/nf-core/modules/tree/master/software @@ -55,7 +55,6 @@ process {{ tool_name_underscore|upper }} { path "versions.yml" , emit: version script: - def software = getSoftwareName(task.process) {% if has_meta -%} def prefix = options.suffix ? "${meta.id}${options.suffix}" : "${meta.id}" {%- endif %} diff --git a/nf_core/modules/lint/functions_nf.py b/nf_core/modules/lint/functions_nf.py index aef0d115ea..f03f0c39a3 100644 --- a/nf_core/modules/lint/functions_nf.py +++ b/nf_core/modules/lint/functions_nf.py @@ -22,7 +22,7 @@ def functions_nf(module_lint_object, module): return # Test whether all required functions are present - required_functions = ["getSoftwareName", "getProcessName", "initOptions", "getPathFromList", "saveFiles"] + required_functions = ["getProcessName", "initOptions", "getPathFromList", "saveFiles"] lines = "\n".join(lines) contains_all_functions = True for f in required_functions: diff --git a/nf_core/pipeline-template/modules/local/functions.nf b/nf_core/pipeline-template/modules/local/functions.nf index 4860a36278..0dfaa3a1ca 100644 --- a/nf_core/pipeline-template/modules/local/functions.nf +++ b/nf_core/pipeline-template/modules/local/functions.nf @@ -2,13 +2,6 @@ // 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() -} - // // Extract name of module from process name using $task.process // diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf index 4860a36278..0dfaa3a1ca 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf @@ -2,13 +2,6 @@ // 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() -} - // // Extract name of module from process name using $task.process // diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index 97183b62c9..8becdcb48d 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -1,5 +1,5 @@ // Import generic module functions -include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' +include { initOptions; saveFiles; getProcessName } from './functions' params.options = [:] options = initOptions(params.options) diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf index 4860a36278..0dfaa3a1ca 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf @@ -2,13 +2,6 @@ // 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() -} - // // Extract name of module from process name using $task.process // diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index 77c8f4361a..a9ba0fe3a2 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -1,5 +1,5 @@ // Import generic module functions -include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' +include { initOptions; saveFiles; getProcessName } from './functions' params.options = [:] options = initOptions(params.options) From 1ee14e9a98608a87391d6b0739950fc85efcff4b Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 11:44:03 +0200 Subject: [PATCH 060/266] remove software var --- .../pipeline-template/modules/nf-core/modules/fastqc/main.nf | 5 ++--- .../modules/nf-core/modules/multiqc/main.nf | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index 8becdcb48d..26eb12cdad 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -28,7 +28,6 @@ process FASTQC { script: // 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}" if (meta.single_end) { """ @@ -37,7 +36,7 @@ process FASTQC { cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - $software: \$(fastqc --version | sed -e "s/FastQC v//g") + fastqc: \$(fastqc --version | sed -e "s/FastQC v//g") END_VERSIONS """ } else { @@ -48,7 +47,7 @@ process FASTQC { cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - $software: \$(fastqc --version | sed -e "s/FastQC v//g") + fastqc: \$(fastqc --version | sed -e "s/FastQC v//g") END_VERSIONS """ } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index a9ba0fe3a2..65b1e64bb7 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -27,13 +27,12 @@ process MULTIQC { path "versions.yml" , emit: version script: - def software = getSoftwareName(task.process) """ multiqc -f $options.args . cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - $software: \$(multiqc --version | sed -e "s/multiqc, version //g") + multiqc: \$(multiqc --version | sed -e "s/multiqc, version //g") END_VERSIONS """ } From a45c66d3b3a9dbdbbab26d07d904bb9e1e55c5e1 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 11:52:12 +0200 Subject: [PATCH 061/266] Rename publish_dir directory --- nf_core/module-template/modules/main.nf | 2 +- .../pipeline-template/modules/nf-core/modules/fastqc/main.nf | 2 +- .../pipeline-template/modules/nf-core/modules/multiqc/main.nf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index b72659f824..21b079ab3e 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -26,7 +26,7 @@ process {{ tool_name_underscore|upper }} { label '{{ process_label }}' publishDir "${params.outdir}", mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getSoftwareName(task.process), meta:{{ 'meta' if has_meta else "[:]" }}, publish_by_meta:{{ "['id']" if has_meta else "[]" }}) } + saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(task.process), meta:{{ 'meta' if has_meta else "[:]" }}, publish_by_meta:{{ "['id']" if has_meta else "[]" }}) } // TODO nf-core: List required Conda package(s). // Software MUST be pinned to channel (i.e. "bioconda"), version (i.e. "1.10"). diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index 26eb12cdad..a725ecce8d 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -9,7 +9,7 @@ process FASTQC { 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']) } + saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(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) { diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index 65b1e64bb7..c97ed17c10 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -8,7 +8,7 @@ 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:[]) } + saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(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) { From 6682211194bb8e923f3a7a1cd603c0c2af997ae3 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 13:13:24 +0200 Subject: [PATCH 062/266] Update nf_core/module-template/modules/main.nf Co-authored-by: Gregor Sturm --- nf_core/module-template/modules/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 21b079ab3e..16d35aa675 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -80,7 +80,7 @@ process {{ tool_name_underscore|upper }} { cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - samtools: \$( samtools --version 2>&1 | sed 's/^.*samtools //; s/Using.*\$// ) + samtools: \$( samtools --version 2>&1 | sed 's/^.*samtools //; s/Using.*\$//' ) END_VERSIONS """ } From 30bbc8aef6ba9f5791d4a8d089af6a3502e9b2d9 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 13:31:24 +0200 Subject: [PATCH 063/266] Update version.txt reference to versions.yml --- nf_core/module-template/modules/functions.nf | 2 +- nf_core/module-template/modules/meta.yml | 2 +- nf_core/pipeline-template/modules/local/functions.nf | 2 +- .../modules/nf-core/modules/fastqc/functions.nf | 2 +- .../pipeline-template/modules/nf-core/modules/fastqc/meta.yml | 2 +- .../modules/nf-core/modules/multiqc/functions.nf | 2 +- .../pipeline-template/modules/nf-core/modules/multiqc/meta.yml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/nf_core/module-template/modules/functions.nf b/nf_core/module-template/modules/functions.nf index 0dfaa3a1ca..8171d73fa4 100644 --- a/nf_core/module-template/modules/functions.nf +++ b/nf_core/module-template/modules/functions.nf @@ -37,7 +37,7 @@ def getPathFromList(path_list) { // Function to save/publish module results // def saveFiles(Map args) { - if (!args.filename.endsWith('.version.txt')) { + if (!args.filename.equals('versions.yml')) { def ioptions = initOptions(args.options) def path_list = [ ioptions.publish_dir ?: args.publish_dir ] if (ioptions.publish_by_meta) { diff --git a/nf_core/module-template/modules/meta.yml b/nf_core/module-template/modules/meta.yml index be6d3e5f93..c42dd613bd 100644 --- a/nf_core/module-template/modules/meta.yml +++ b/nf_core/module-template/modules/meta.yml @@ -40,7 +40,7 @@ output: - version: type: file description: File containing software version - pattern: "*.{version.txt}" + pattern: "versions.yml" ## TODO nf-core: Delete / customise this example output - bam: type: file diff --git a/nf_core/pipeline-template/modules/local/functions.nf b/nf_core/pipeline-template/modules/local/functions.nf index 0dfaa3a1ca..8171d73fa4 100644 --- a/nf_core/pipeline-template/modules/local/functions.nf +++ b/nf_core/pipeline-template/modules/local/functions.nf @@ -37,7 +37,7 @@ def getPathFromList(path_list) { // Function to save/publish module results // def saveFiles(Map args) { - if (!args.filename.endsWith('.version.txt')) { + if (!args.filename.equals('versions.yml')) { def ioptions = initOptions(args.options) def path_list = [ ioptions.publish_dir ?: args.publish_dir ] if (ioptions.publish_by_meta) { diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf index 0dfaa3a1ca..8171d73fa4 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf @@ -37,7 +37,7 @@ def getPathFromList(path_list) { // Function to save/publish module results // def saveFiles(Map args) { - if (!args.filename.endsWith('.version.txt')) { + if (!args.filename.equals('versions.yml')) { def ioptions = initOptions(args.options) def path_list = [ ioptions.publish_dir ?: args.publish_dir ] if (ioptions.publish_by_meta) { diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml index 8eb9953dce..48031356b5 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml @@ -43,7 +43,7 @@ output: - version: type: file description: File containing software version - pattern: "*.{version.txt}" + pattern: "versions.yml" authors: - "@drpatelh" - "@grst" diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf index 0dfaa3a1ca..8171d73fa4 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf @@ -37,7 +37,7 @@ def getPathFromList(path_list) { // Function to save/publish module results // def saveFiles(Map args) { - if (!args.filename.endsWith('.version.txt')) { + if (!args.filename.equals('versions.yml')) { def ioptions = initOptions(args.options) def path_list = [ ioptions.publish_dir ?: args.publish_dir ] if (ioptions.publish_by_meta) { diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml index 532a8bb1ef..2d99ec0d12 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml @@ -32,7 +32,7 @@ output: - version: type: file description: File containing software version - pattern: "*.{version.txt}" + pattern: "versions.yml" authors: - "@abhi18av" - "@bunop" From a98588b0a25eb98fb76cfdccfedf25699873963b Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 13:34:13 +0200 Subject: [PATCH 064/266] Update lint check for software --- nf_core/modules/lint/main_nf.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index 018dc99af2..ac29e9ff26 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -116,16 +116,10 @@ def main_nf(module_lint_object, module): def check_script_section(self, lines): """ Lint the script section - Checks whether 'def sotware' and 'def prefix' are defined + Checks whether 'def prefix' is defined """ script = "".join(lines) - # check for software - if re.search("\s*def\s*software\s*=\s*getSoftwareName", script): - self.passed.append(("main_nf_version_script", "Software version specified in script section", self.main_nf)) - else: - self.warned.append(("main_nf_version_script", "Software version unspecified in script section", self.main_nf)) - # check for prefix (only if module has a meta map as input) if self.has_meta: if re.search("\s*prefix\s*=\s*options.suffix", script): From c46e2194e91f1878b3a714550eec6bf8b7d5c40e Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Thu, 12 Aug 2021 14:28:25 +0200 Subject: [PATCH 065/266] Make publish_dirs lowercase --- nf_core/module-template/modules/main.nf | 2 +- .../pipeline-template/modules/nf-core/modules/fastqc/main.nf | 2 +- .../pipeline-template/modules/nf-core/modules/multiqc/main.nf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 16d35aa675..4047c0bf04 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -26,7 +26,7 @@ process {{ tool_name_underscore|upper }} { label '{{ process_label }}' publishDir "${params.outdir}", mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(task.process), meta:{{ 'meta' if has_meta else "[:]" }}, publish_by_meta:{{ "['id']" if has_meta else "[]" }}) } + saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(task.process).toLowerCase(), meta:{{ 'meta' if has_meta else "[:]" }}, publish_by_meta:{{ "['id']" if has_meta else "[]" }}) } // TODO nf-core: List required Conda package(s). // Software MUST be pinned to channel (i.e. "bioconda"), version (i.e. "1.10"). diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index a725ecce8d..35ed481c9c 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -9,7 +9,7 @@ process FASTQC { label 'process_medium' publishDir "${params.outdir}", mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(task.process), meta:meta, publish_by_meta:['id']) } + saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(task.process).toLowerCase(), 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) { diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index c97ed17c10..2fc36ad1f3 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -8,7 +8,7 @@ process MULTIQC { label 'process_medium' publishDir "${params.outdir}", mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(task.process), meta:[:], publish_by_meta:[]) } + saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(task.process).toLowerCase(), meta:[:], publish_by_meta:[]) } conda (params.enable_conda ? "bioconda::multiqc=1.10.1" : null) if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) { From 77a89e2b6ac18952c2fbf96a9601292587a1160e Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Fri, 13 Aug 2021 09:17:06 +0200 Subject: [PATCH 066/266] Revert "Make publish_dirs lowercase" This reverts commit c46e2194e91f1878b3a714550eec6bf8b7d5c40e. --- nf_core/module-template/modules/main.nf | 2 +- .../pipeline-template/modules/nf-core/modules/fastqc/main.nf | 2 +- .../pipeline-template/modules/nf-core/modules/multiqc/main.nf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 4047c0bf04..16d35aa675 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -26,7 +26,7 @@ process {{ tool_name_underscore|upper }} { label '{{ process_label }}' publishDir "${params.outdir}", mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(task.process).toLowerCase(), meta:{{ 'meta' if has_meta else "[:]" }}, publish_by_meta:{{ "['id']" if has_meta else "[]" }}) } + saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(task.process), meta:{{ 'meta' if has_meta else "[:]" }}, publish_by_meta:{{ "['id']" if has_meta else "[]" }}) } // TODO nf-core: List required Conda package(s). // Software MUST be pinned to channel (i.e. "bioconda"), version (i.e. "1.10"). diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index 35ed481c9c..a725ecce8d 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -9,7 +9,7 @@ process FASTQC { label 'process_medium' publishDir "${params.outdir}", mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(task.process).toLowerCase(), meta:meta, publish_by_meta:['id']) } + saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(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) { diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index 2fc36ad1f3..c97ed17c10 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -8,7 +8,7 @@ process MULTIQC { label 'process_medium' publishDir "${params.outdir}", mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(task.process).toLowerCase(), meta:[:], publish_by_meta:[]) } + saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(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) { From bd394ba3a7f6e6e5f3f99e252c506d83ce92fe82 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Fri, 13 Aug 2021 09:17:28 +0200 Subject: [PATCH 067/266] Revert "Update lint check for software" This reverts commit a98588b0a25eb98fb76cfdccfedf25699873963b. --- nf_core/modules/lint/main_nf.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index ac29e9ff26..018dc99af2 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -116,10 +116,16 @@ def main_nf(module_lint_object, module): def check_script_section(self, lines): """ Lint the script section - Checks whether 'def prefix' is defined + Checks whether 'def sotware' and 'def prefix' are defined """ script = "".join(lines) + # check for software + if re.search("\s*def\s*software\s*=\s*getSoftwareName", script): + self.passed.append(("main_nf_version_script", "Software version specified in script section", self.main_nf)) + else: + self.warned.append(("main_nf_version_script", "Software version unspecified in script section", self.main_nf)) + # check for prefix (only if module has a meta map as input) if self.has_meta: if re.search("\s*prefix\s*=\s*options.suffix", script): From 610585cbcdc47390744224e525298be983b96a7f Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Fri, 13 Aug 2021 09:17:44 +0200 Subject: [PATCH 068/266] Revert "Rename publish_dir directory" This reverts commit a45c66d3b3a9dbdbbab26d07d904bb9e1e55c5e1. --- nf_core/module-template/modules/main.nf | 2 +- .../pipeline-template/modules/nf-core/modules/fastqc/main.nf | 2 +- .../pipeline-template/modules/nf-core/modules/multiqc/main.nf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 16d35aa675..b91d44307b 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -26,7 +26,7 @@ process {{ tool_name_underscore|upper }} { label '{{ process_label }}' publishDir "${params.outdir}", mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(task.process), meta:{{ 'meta' if has_meta else "[:]" }}, publish_by_meta:{{ "['id']" if has_meta else "[]" }}) } + saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getSoftwareName(task.process), meta:{{ 'meta' if has_meta else "[:]" }}, publish_by_meta:{{ "['id']" if has_meta else "[]" }}) } // TODO nf-core: List required Conda package(s). // Software MUST be pinned to channel (i.e. "bioconda"), version (i.e. "1.10"). diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index a725ecce8d..26eb12cdad 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -9,7 +9,7 @@ process FASTQC { label 'process_medium' publishDir "${params.outdir}", mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(task.process), meta:meta, publish_by_meta:['id']) } + 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) { diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index c97ed17c10..65b1e64bb7 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -8,7 +8,7 @@ process MULTIQC { label 'process_medium' publishDir "${params.outdir}", mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getProcessName(task.process), meta:[:], publish_by_meta:[]) } + 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) { From 21983765f28e432273285eaa8f013dc1ac9dc7df Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Fri, 13 Aug 2021 09:18:01 +0200 Subject: [PATCH 069/266] Revert "remove software var" This reverts commit 1ee14e9a98608a87391d6b0739950fc85efcff4b. --- .../pipeline-template/modules/nf-core/modules/fastqc/main.nf | 5 +++-- .../modules/nf-core/modules/multiqc/main.nf | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index 26eb12cdad..8becdcb48d 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -28,6 +28,7 @@ process FASTQC { script: // 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}" if (meta.single_end) { """ @@ -36,7 +37,7 @@ process FASTQC { cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - fastqc: \$(fastqc --version | sed -e "s/FastQC v//g") + $software: \$(fastqc --version | sed -e "s/FastQC v//g") END_VERSIONS """ } else { @@ -47,7 +48,7 @@ process FASTQC { cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - fastqc: \$(fastqc --version | sed -e "s/FastQC v//g") + $software: \$(fastqc --version | sed -e "s/FastQC v//g") END_VERSIONS """ } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index 65b1e64bb7..a9ba0fe3a2 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -27,12 +27,13 @@ process MULTIQC { path "versions.yml" , emit: version script: + def software = getSoftwareName(task.process) """ multiqc -f $options.args . cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - multiqc: \$(multiqc --version | sed -e "s/multiqc, version //g") + $software: \$(multiqc --version | sed -e "s/multiqc, version //g") END_VERSIONS """ } From 42404591f04cc614a70b225aacbb9c6828c64540 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Fri, 13 Aug 2021 09:18:12 +0200 Subject: [PATCH 070/266] Revert "Remove getSoftwareName function" This reverts commit 70d14aaf950c0e825310e1229017b01ccacc6662. --- nf_core/module-template/modules/functions.nf | 7 +++++++ nf_core/module-template/modules/main.nf | 3 ++- nf_core/modules/lint/functions_nf.py | 2 +- nf_core/pipeline-template/modules/local/functions.nf | 7 +++++++ .../modules/nf-core/modules/fastqc/functions.nf | 7 +++++++ .../modules/nf-core/modules/fastqc/main.nf | 2 +- .../modules/nf-core/modules/multiqc/functions.nf | 7 +++++++ .../modules/nf-core/modules/multiqc/main.nf | 2 +- 8 files changed, 33 insertions(+), 4 deletions(-) diff --git a/nf_core/module-template/modules/functions.nf b/nf_core/module-template/modules/functions.nf index 8171d73fa4..9fbf2c4992 100644 --- a/nf_core/module-template/modules/functions.nf +++ b/nf_core/module-template/modules/functions.nf @@ -2,6 +2,13 @@ // 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() +} + // // Extract name of module from process name using $task.process // diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index b91d44307b..460f5f1b76 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -1,5 +1,5 @@ // Import generic module functions -include { initOptions; saveFiles; getProcessName } from './functions' +include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' // TODO nf-core: If in doubt look at other nf-core/modules to see how we are doing things! :) // https://github.com/nf-core/modules/tree/master/software @@ -55,6 +55,7 @@ process {{ tool_name_underscore|upper }} { path "versions.yml" , emit: version script: + def software = getSoftwareName(task.process) {% if has_meta -%} def prefix = options.suffix ? "${meta.id}${options.suffix}" : "${meta.id}" {%- endif %} diff --git a/nf_core/modules/lint/functions_nf.py b/nf_core/modules/lint/functions_nf.py index f03f0c39a3..aef0d115ea 100644 --- a/nf_core/modules/lint/functions_nf.py +++ b/nf_core/modules/lint/functions_nf.py @@ -22,7 +22,7 @@ def functions_nf(module_lint_object, module): return # Test whether all required functions are present - required_functions = ["getProcessName", "initOptions", "getPathFromList", "saveFiles"] + required_functions = ["getSoftwareName", "getProcessName", "initOptions", "getPathFromList", "saveFiles"] lines = "\n".join(lines) contains_all_functions = True for f in required_functions: diff --git a/nf_core/pipeline-template/modules/local/functions.nf b/nf_core/pipeline-template/modules/local/functions.nf index 8171d73fa4..9fbf2c4992 100644 --- a/nf_core/pipeline-template/modules/local/functions.nf +++ b/nf_core/pipeline-template/modules/local/functions.nf @@ -2,6 +2,13 @@ // 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() +} + // // Extract name of module from process name using $task.process // diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf index 8171d73fa4..9fbf2c4992 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf @@ -2,6 +2,13 @@ // 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() +} + // // Extract name of module from process name using $task.process // diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index 8becdcb48d..97183b62c9 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -1,5 +1,5 @@ // Import generic module functions -include { initOptions; saveFiles; getProcessName } from './functions' +include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' params.options = [:] options = initOptions(params.options) diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf index 8171d73fa4..9fbf2c4992 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf @@ -2,6 +2,13 @@ // 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() +} + // // Extract name of module from process name using $task.process // diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index a9ba0fe3a2..77c8f4361a 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -1,5 +1,5 @@ // Import generic module functions -include { initOptions; saveFiles; getProcessName } from './functions' +include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' params.options = [:] options = initOptions(params.options) From 3bf1b7f76230807c3291a9f40f221e7061d10971 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Fri, 13 Aug 2021 09:25:44 +0200 Subject: [PATCH 071/266] Remove def software line --- nf_core/module-template/modules/main.nf | 1 - nf_core/modules/lint/main_nf.py | 6 ------ .../modules/nf-core/modules/fastqc/main.nf | 1 - .../modules/nf-core/modules/multiqc/main.nf | 1 - 4 files changed, 9 deletions(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 460f5f1b76..457f2b39bc 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -55,7 +55,6 @@ process {{ tool_name_underscore|upper }} { path "versions.yml" , emit: version script: - def software = getSoftwareName(task.process) {% if has_meta -%} def prefix = options.suffix ? "${meta.id}${options.suffix}" : "${meta.id}" {%- endif %} diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index 018dc99af2..0393d352eb 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -120,12 +120,6 @@ def check_script_section(self, lines): """ script = "".join(lines) - # check for software - if re.search("\s*def\s*software\s*=\s*getSoftwareName", script): - self.passed.append(("main_nf_version_script", "Software version specified in script section", self.main_nf)) - else: - self.warned.append(("main_nf_version_script", "Software version unspecified in script section", self.main_nf)) - # check for prefix (only if module has a meta map as input) if self.has_meta: if re.search("\s*prefix\s*=\s*options.suffix", script): diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index 97183b62c9..07585128ec 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -28,7 +28,6 @@ process FASTQC { script: // 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}" if (meta.single_end) { """ diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index 77c8f4361a..a92cca59b0 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -27,7 +27,6 @@ process MULTIQC { path "versions.yml" , emit: version script: - def software = getSoftwareName(task.process) """ multiqc -f $options.args . From d965d62d5fb894a8bd12460b19dc7a1f2722872d Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Fri, 13 Aug 2021 09:30:14 +0200 Subject: [PATCH 072/266] Remove reference to $software --- .../pipeline-template/modules/nf-core/modules/fastqc/main.nf | 4 ++-- .../pipeline-template/modules/nf-core/modules/multiqc/main.nf | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index 07585128ec..8173cd8962 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -36,7 +36,7 @@ process FASTQC { cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - $software: \$(fastqc --version | sed -e "s/FastQC v//g") + fastqc: \$(fastqc --version | sed -e "s/FastQC v//g") END_VERSIONS """ } else { @@ -47,7 +47,7 @@ process FASTQC { cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - $software: \$(fastqc --version | sed -e "s/FastQC v//g") + fastqc: \$(fastqc --version | sed -e "s/FastQC v//g") END_VERSIONS """ } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index a92cca59b0..d1e26b6fbe 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -32,7 +32,7 @@ process MULTIQC { cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - $software: \$(multiqc --version | sed -e "s/multiqc, version //g") + multiqc: \$(multiqc --version | sed -e "s/multiqc, version //g") END_VERSIONS """ } From a3c49ef39a58b54836f2aeae74f46b70092cbd9e Mon Sep 17 00:00:00 2001 From: "Robert A. Petit III" Date: Mon, 6 Sep 2021 12:21:06 -0600 Subject: [PATCH 073/266] display enum choices on error --- .../pipeline-template/lib/NfcoreSchema.groovy | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/nf_core/pipeline-template/lib/NfcoreSchema.groovy b/nf_core/pipeline-template/lib/NfcoreSchema.groovy index 8d6920dd64..07a2387a38 100755 --- a/nf_core/pipeline-template/lib/NfcoreSchema.groovy +++ b/nf_core/pipeline-template/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 } @@ -330,7 +334,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,7 +350,16 @@ 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) { From 12103eb2675937b3d0c64510858f4074a56d3165 Mon Sep 17 00:00:00 2001 From: "Robert A. Petit III" Date: Mon, 6 Sep 2021 12:27:20 -0600 Subject: [PATCH 074/266] fix typo --- nf_core/pipeline-template/lib/NfcoreSchema.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/lib/NfcoreSchema.groovy b/nf_core/pipeline-template/lib/NfcoreSchema.groovy index 07a2387a38..4ee0b989b5 100755 --- a/nf_core/pipeline-template/lib/NfcoreSchema.groovy +++ b/nf_core/pipeline-template/lib/NfcoreSchema.groovy @@ -355,7 +355,7 @@ class NfcoreSchema { 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(', ')})" + log.error "${error_msg}: ${enums[param].join(', ')})" } } else { log.error "* --${param}: ${ex_json['message']} (${param_val})" From 462cb651eca494718075c1c352732b199d8b5b3d Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 7 Sep 2021 09:58:43 +0200 Subject: [PATCH 075/266] Catch AssertionError from schema build Fixes bug found by @erikrikarddaniel where error from `nextflow config` raised an `AssertionError` that was not handled. --- nf_core/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/__main__.py b/nf_core/__main__.py index a9b5f05b68..a9500509a4 100755 --- a/nf_core/__main__.py +++ b/nf_core/__main__.py @@ -668,7 +668,7 @@ def build(dir, no_prompts, web_only, url): schema_obj = nf_core.schema.PipelineSchema() if schema_obj.build_schema(dir, no_prompts, web_only, url) is False: sys.exit(1) - except UserWarning as e: + except (UserWarning, AssertionError) as e: log.error(e) sys.exit(1) From 251f51a6e69bf89584165536b6c0a75f35fe724b Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 7 Sep 2021 11:07:00 +0200 Subject: [PATCH 076/266] Update black GitHub Action Use official black GitHub Action --- .github/workflows/python-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml index cf41eb67da..b75327e050 100644 --- a/.github/workflows/python-lint.yml +++ b/.github/workflows/python-lint.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v2 - name: Check code lints with Black - uses: jpetrucciani/black-check@master + uses: psf/black@stable # If the above check failed, post a comment on the PR explaining the failure - name: Post PR comment From f27d0813ae4ff8108bc35ed97414b49a109271eb Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Wed, 8 Sep 2021 15:52:39 +0200 Subject: [PATCH 077/266] Update multiqc link --- nf_core/pipeline-template/CITATIONS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/CITATIONS.md b/nf_core/pipeline-template/CITATIONS.md index e84b929891..323c681a0a 100644 --- a/nf_core/pipeline-template/CITATIONS.md +++ b/nf_core/pipeline-template/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 From 8635612b3ee3b1e414414553c4fd72cb048464d3 Mon Sep 17 00:00:00 2001 From: Mahesh Binzer-Panchal Date: Tue, 14 Sep 2021 15:06:48 +0200 Subject: [PATCH 078/266] Update output docs (software_versions.tsv ->software_versions.yml) --- nf_core/pipeline-template/docs/output.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/docs/output.md b/nf_core/pipeline-template/docs/output.md index 9646e12290..9fd9e5e127 100644 --- a/nf_core/pipeline-template/docs/output.md +++ b/nf_core/pipeline-template/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`. * Reformatted samplesheet files used as input to the pipeline: `samplesheet.valid.csv`. From 2af8709fc757c65e2191db0151e48f574d273386 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Thu, 23 Sep 2021 13:17:42 +0200 Subject: [PATCH 079/266] Do not update nf-core modules in pipeline template yet --- .../modules/nf-core/modules/fastqc/functions.nf | 9 +-------- .../modules/nf-core/modules/fastqc/main.nf | 17 +++++------------ .../modules/nf-core/modules/fastqc/meta.yml | 2 +- .../nf-core/modules/multiqc/functions.nf | 9 +-------- .../modules/nf-core/modules/multiqc/main.nf | 11 ++++------- .../modules/nf-core/modules/multiqc/meta.yml | 2 +- 6 files changed, 13 insertions(+), 37 deletions(-) diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf index 9fbf2c4992..da9da093d3 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf @@ -9,13 +9,6 @@ def getSoftwareName(task_process) { return task_process.tokenize(':')[-1].tokenize('_')[0].toLowerCase() } -// -// Extract name of module from process name using $task.process -// -def getProcessName(task_process) { - return task_process.tokenize(':')[-1] -} - // // Function to initialise default values and to generate a Groovy Map of available options for nf-core modules // @@ -44,7 +37,7 @@ def getPathFromList(path_list) { // Function to save/publish module results // def saveFiles(Map args) { - if (!args.filename.equals('versions.yml')) { + 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) { diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index 8173cd8962..39c327b261 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -1,5 +1,5 @@ // Import generic module functions -include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' +include { initOptions; saveFiles; getSoftwareName } from './functions' params.options = [:] options = initOptions(params.options) @@ -24,31 +24,24 @@ process FASTQC { output: tuple val(meta), path("*.html"), emit: html tuple val(meta), path("*.zip") , emit: zip - path "versions.yml" , emit: version + path "*.version.txt" , emit: version script: // 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}" if (meta.single_end) { """ [ ! -f ${prefix}.fastq.gz ] && ln -s $reads ${prefix}.fastq.gz fastqc $options.args --threads $task.cpus ${prefix}.fastq.gz - - cat <<-END_VERSIONS > versions.yml - ${getProcessName(task.process)}: - fastqc: \$(fastqc --version | sed -e "s/FastQC v//g") - END_VERSIONS + fastqc --version | sed -e "s/FastQC v//g" > ${software}.version.txt """ } 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 - - cat <<-END_VERSIONS > versions.yml - ${getProcessName(task.process)}: - fastqc: \$(fastqc --version | sed -e "s/FastQC v//g") - END_VERSIONS + fastqc --version | sed -e "s/FastQC v//g" > ${software}.version.txt """ } } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml index 48031356b5..8eb9953dce 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml @@ -43,7 +43,7 @@ output: - version: type: file description: File containing software version - pattern: "versions.yml" + pattern: "*.{version.txt}" authors: - "@drpatelh" - "@grst" diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf index 9fbf2c4992..da9da093d3 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf @@ -9,13 +9,6 @@ def getSoftwareName(task_process) { return task_process.tokenize(':')[-1].tokenize('_')[0].toLowerCase() } -// -// Extract name of module from process name using $task.process -// -def getProcessName(task_process) { - return task_process.tokenize(':')[-1] -} - // // Function to initialise default values and to generate a Groovy Map of available options for nf-core modules // @@ -44,7 +37,7 @@ def getPathFromList(path_list) { // Function to save/publish module results // def saveFiles(Map args) { - if (!args.filename.equals('versions.yml')) { + 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) { diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index d1e26b6fbe..da78080024 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -1,5 +1,5 @@ // Import generic module functions -include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' +include { initOptions; saveFiles; getSoftwareName } from './functions' params.options = [:] options = initOptions(params.options) @@ -24,15 +24,12 @@ process MULTIQC { path "*multiqc_report.html", emit: report path "*_data" , emit: data path "*_plots" , optional:true, emit: plots - path "versions.yml" , emit: version + path "*.version.txt" , emit: version script: + def software = getSoftwareName(task.process) """ multiqc -f $options.args . - - cat <<-END_VERSIONS > versions.yml - ${getProcessName(task.process)}: - multiqc: \$(multiqc --version | sed -e "s/multiqc, version //g") - END_VERSIONS + multiqc --version | sed -e "s/multiqc, version //g" > ${software}.version.txt """ } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml index 2d99ec0d12..532a8bb1ef 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml @@ -32,7 +32,7 @@ output: - version: type: file description: File containing software version - pattern: "versions.yml" + pattern: "*.{version.txt}" authors: - "@abhi18av" - "@bunop" From 6c52385764a2c8a736f7199919b5979041fc8530 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Thu, 23 Sep 2021 13:19:57 +0200 Subject: [PATCH 080/266] Update functions.nf in module template and in local pipeline modules --- nf_core/module-template/modules/functions.nf | 47 ++++++++++--------- .../modules/local/functions.nf | 47 ++++++++++--------- 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/nf_core/module-template/modules/functions.nf b/nf_core/module-template/modules/functions.nf index 9fbf2c4992..85628ee0eb 100644 --- a/nf_core/module-template/modules/functions.nf +++ b/nf_core/module-template/modules/functions.nf @@ -44,32 +44,35 @@ def getPathFromList(path_list) { // Function to save/publish module results // def saveFiles(Map args) { - if (!args.filename.equals('versions.yml')) { - 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) + def ioptions = initOptions(args.options) + def path_list = [ ioptions.publish_dir ?: args.publish_dir ] + + // Do not publish versions.yml unless running from pytest workflow + if (args.filename.equals('versions.yml') && !System.getenv("NF_CORE_MODULES_TEST")) { + return null + } + 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" - } + } + 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" } + } else if (ioptions.publish_files == null) { + return "${getPathFromList(path_list)}/$args.filename" } } diff --git a/nf_core/pipeline-template/modules/local/functions.nf b/nf_core/pipeline-template/modules/local/functions.nf index 9fbf2c4992..85628ee0eb 100644 --- a/nf_core/pipeline-template/modules/local/functions.nf +++ b/nf_core/pipeline-template/modules/local/functions.nf @@ -44,32 +44,35 @@ def getPathFromList(path_list) { // Function to save/publish module results // def saveFiles(Map args) { - if (!args.filename.equals('versions.yml')) { - 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) + def ioptions = initOptions(args.options) + def path_list = [ ioptions.publish_dir ?: args.publish_dir ] + + // Do not publish versions.yml unless running from pytest workflow + if (args.filename.equals('versions.yml') && !System.getenv("NF_CORE_MODULES_TEST")) { + return null + } + 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" - } + } + 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" } + } else if (ioptions.publish_files == null) { + return "${getPathFromList(path_list)}/$args.filename" } } From da8dadba76bd02343b16d9b89561b92e78a0b9c8 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Thu, 23 Sep 2021 13:31:58 +0200 Subject: [PATCH 081/266] Show linting messages when linting tests failed --- tests/modules/lint.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/modules/lint.py b/tests/modules/lint.py index de29371c58..1cf99c07b9 100644 --- a/tests/modules/lint.py +++ b/tests/modules/lint.py @@ -8,7 +8,7 @@ def test_modules_lint_trimgalore(self): module_lint.lint(print_results=False, module="trimgalore") assert len(module_lint.passed) > 0 assert len(module_lint.warned) >= 0 - assert len(module_lint.failed) == 0 + assert len(module_lint.failed) == 0, module_lint.failed def test_modules_lint_empty(self): @@ -19,7 +19,7 @@ def test_modules_lint_empty(self): module_lint.lint(print_results=False, all_modules=True) assert len(module_lint.passed) == 0 assert len(module_lint.warned) == 0 - assert len(module_lint.failed) == 0 + assert len(module_lint.failed) == 0, module_lint.failed def test_modules_lint_new_modules(self): @@ -28,4 +28,4 @@ def test_modules_lint_new_modules(self): module_lint.lint(print_results=True, all_modules=True) assert len(module_lint.passed) > 0 assert len(module_lint.warned) >= 0 - assert len(module_lint.failed) == 0 + assert len(module_lint.failed) == 0, module_lint.failed From 9a0dfd9e0e83dd588cbfe6f09b618c96f620b7d3 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Thu, 23 Sep 2021 13:41:06 +0200 Subject: [PATCH 082/266] Print linting error message --- tests/modules/lint.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/modules/lint.py b/tests/modules/lint.py index 1cf99c07b9..8371b92fb7 100644 --- a/tests/modules/lint.py +++ b/tests/modules/lint.py @@ -8,7 +8,7 @@ def test_modules_lint_trimgalore(self): module_lint.lint(print_results=False, module="trimgalore") assert len(module_lint.passed) > 0 assert len(module_lint.warned) >= 0 - assert len(module_lint.failed) == 0, module_lint.failed + assert len(module_lint.failed) == 0, f"Linting failed with {[x.__dict__ for x in module_lint.failed]}" def test_modules_lint_empty(self): @@ -19,7 +19,7 @@ def test_modules_lint_empty(self): module_lint.lint(print_results=False, all_modules=True) assert len(module_lint.passed) == 0 assert len(module_lint.warned) == 0 - assert len(module_lint.failed) == 0, module_lint.failed + assert len(module_lint.failed) == 0, f"Linting failed with {[x.__dict__ for x in module_lint.failed]}" def test_modules_lint_new_modules(self): @@ -28,4 +28,4 @@ def test_modules_lint_new_modules(self): module_lint.lint(print_results=True, all_modules=True) assert len(module_lint.passed) > 0 assert len(module_lint.warned) >= 0 - assert len(module_lint.failed) == 0, module_lint.failed + assert len(module_lint.failed) == 0, f"Linting failed with {[x.__dict__ for x in module_lint.failed]}" From 82ce4b4679f026c802cf41f2e78a3e0a87d271c6 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Thu, 23 Sep 2021 15:39:31 +0200 Subject: [PATCH 083/266] Update nf_core/pipeline-template/docs/output.md Co-authored-by: Harshil Patel --- nf_core/pipeline-template/docs/output.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/docs/output.md b/nf_core/pipeline-template/docs/output.md index 9fd9e5e127..4ef9a4ea01 100644 --- a/nf_core/pipeline-template/docs/output.md +++ b/nf_core/pipeline-template/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.yml`. + * 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`. From 13071abfde27313ab24324b6c5a228a311907fac Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Sat, 25 Sep 2021 19:32:49 +0200 Subject: [PATCH 084/266] Update main_nf.py --- nf_core/modules/lint/main_nf.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index 0393d352eb..cda47fcfbd 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -116,10 +116,16 @@ def main_nf(module_lint_object, module): def check_script_section(self, lines): """ Lint the script section - Checks whether 'def sotware' and 'def prefix' are defined + Checks whether 'def prefix' is defined and whether getProcessName is used for `versions.yml`. """ script = "".join(lines) + # check that process name is used for `versions.yml` + if re.search("\${\s*getProcessName(\s*task.process\s*)\s*}", script): + self.passed.append(("main_nf_version_script", "Process name used for versions.yml", self.main_nf)) + else: + self.failed.append(("main_nf_version_script", "Process name not used for versions.yml", self.main_nf)) + # check for prefix (only if module has a meta map as input) if self.has_meta: if re.search("\s*prefix\s*=\s*options.suffix", script): From 5b5f1b94f2b8ac7983fd7871d7c5a5e8427ed770 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Sat, 25 Sep 2021 19:46:43 +0200 Subject: [PATCH 085/266] update regex --- nf_core/modules/lint/main_nf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index cda47fcfbd..4b63295c20 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -121,7 +121,7 @@ def check_script_section(self, lines): script = "".join(lines) # check that process name is used for `versions.yml` - if re.search("\${\s*getProcessName(\s*task.process\s*)\s*}", script): + if re.search("\$\{\s*getProcessName\s*\(\s*task\.process\s*\)\s*\}", script): self.passed.append(("main_nf_version_script", "Process name used for versions.yml", self.main_nf)) else: self.failed.append(("main_nf_version_script", "Process name not used for versions.yml", self.main_nf)) From f0fe09e81009c5197a77d53aa6464601690ff721 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Wed, 29 Sep 2021 13:22:02 +0100 Subject: [PATCH 086/266] Fix #1275 --- nf_core/pipeline-template/modules.json | 6 +-- .../nf-core/modules/fastqc/functions.nf | 54 +++++++++++-------- .../modules/nf-core/modules/fastqc/main.nf | 17 ++++-- .../modules/nf-core/modules/fastqc/meta.yml | 2 +- .../nf-core/modules/multiqc/functions.nf | 54 +++++++++++-------- .../modules/nf-core/modules/multiqc/main.nf | 16 +++--- .../modules/nf-core/modules/multiqc/meta.yml | 2 +- 7 files changed, 91 insertions(+), 60 deletions(-) diff --git a/nf_core/pipeline-template/modules.json b/nf_core/pipeline-template/modules.json index 3cb20a2e0e..5dd4e438d9 100644 --- a/nf_core/pipeline-template/modules.json +++ b/nf_core/pipeline-template/modules.json @@ -4,11 +4,11 @@ "repos": { "nf-core/modules": { "fastqc": { - "git_sha": "e937c7950af70930d1f34bb961403d9d2aa81c7d" + "git_sha": "ab67a1d41b63bf52fd7c147f7f8f6e8d167590b5" }, "multiqc": { - "git_sha": "e937c7950af70930d1f34bb961403d9d2aa81c7d" + "git_sha": "ab67a1d41b63bf52fd7c147f7f8f6e8d167590b5" } } } -} +} \ No newline at end of file diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf index da9da093d3..85628ee0eb 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf @@ -9,6 +9,13 @@ def getSoftwareName(task_process) { return task_process.tokenize(':')[-1].tokenize('_')[0].toLowerCase() } +// +// Extract name of module from process name using $task.process +// +def getProcessName(task_process) { + return task_process.tokenize(':')[-1] +} + // // Function to initialise default values and to generate a Groovy Map of available options for nf-core modules // @@ -37,32 +44,35 @@ def getPathFromList(path_list) { // 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) + def ioptions = initOptions(args.options) + def path_list = [ ioptions.publish_dir ?: args.publish_dir ] + + // Do not publish versions.yml unless running from pytest workflow + if (args.filename.equals('versions.yml') && !System.getenv("NF_CORE_MODULES_TEST")) { + return null + } + 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" - } + } + 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" } + } else if (ioptions.publish_files == null) { + return "${getPathFromList(path_list)}/$args.filename" } } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index 39c327b261..88bfbf5b02 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -1,5 +1,5 @@ // Import generic module functions -include { initOptions; saveFiles; getSoftwareName } from './functions' +include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' params.options = [:] options = initOptions(params.options) @@ -24,24 +24,31 @@ 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: version script: // 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}" 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 + + cat <<-END_VERSIONS > versions.yml + ${getProcessName(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 + + cat <<-END_VERSIONS > versions.yml + ${getProcessName(task.process)}: + fastqc: \$( fastqc --version | sed -e "s/FastQC v//g" ) + END_VERSIONS """ } } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml index 8eb9953dce..48031356b5 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml @@ -43,7 +43,7 @@ output: - version: type: file description: File containing software version - pattern: "*.{version.txt}" + pattern: "versions.yml" authors: - "@drpatelh" - "@grst" diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf index da9da093d3..85628ee0eb 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf @@ -9,6 +9,13 @@ def getSoftwareName(task_process) { return task_process.tokenize(':')[-1].tokenize('_')[0].toLowerCase() } +// +// Extract name of module from process name using $task.process +// +def getProcessName(task_process) { + return task_process.tokenize(':')[-1] +} + // // Function to initialise default values and to generate a Groovy Map of available options for nf-core modules // @@ -37,32 +44,35 @@ def getPathFromList(path_list) { // 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) + def ioptions = initOptions(args.options) + def path_list = [ ioptions.publish_dir ?: args.publish_dir ] + + // Do not publish versions.yml unless running from pytest workflow + if (args.filename.equals('versions.yml') && !System.getenv("NF_CORE_MODULES_TEST")) { + return null + } + 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" - } + } + 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" } + } else if (ioptions.publish_files == null) { + return "${getPathFromList(path_list)}/$args.filename" } } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index da78080024..2e7ad932e5 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -1,5 +1,5 @@ // Import generic module functions -include { initOptions; saveFiles; getSoftwareName } from './functions' +include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' params.options = [:] options = initOptions(params.options) @@ -10,11 +10,11 @@ process MULTIQC { 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) + conda (params.enable_conda ? 'bioconda::multiqc=1.11' : null) if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) { - container "https://depot.galaxyproject.org/singularity/multiqc:1.10.1--py_0" + container "https://depot.galaxyproject.org/singularity/multiqc:1.11--pyhdfd78af_0" } else { - container "quay.io/biocontainers/multiqc:1.10.1--py_0" + container "quay.io/biocontainers/multiqc:1.11--pyhdfd78af_0" } input: @@ -24,12 +24,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: version script: def software = getSoftwareName(task.process) """ multiqc -f $options.args . - multiqc --version | sed -e "s/multiqc, version //g" > ${software}.version.txt + + cat <<-END_VERSIONS > versions.yml + ${getProcessName(task.process)}: + multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) + END_VERSIONS """ } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml index 532a8bb1ef..2d99ec0d12 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml @@ -32,7 +32,7 @@ output: - version: type: file description: File containing software version - pattern: "*.{version.txt}" + pattern: "versions.yml" authors: - "@abhi18av" - "@bunop" From bedd720f3cf25bb4cca428a18078c57da4f4f70a Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Wed, 29 Sep 2021 14:28:56 +0100 Subject: [PATCH 087/266] Install custom/dumpsoftwareversions from nf-core/modules --- nf_core/pipeline-template/conf/base.config | 3 + nf_core/pipeline-template/modules.json | 3 + .../custom/dumpsoftwareversions/functions.nf | 78 +++++++++++++++++++ .../custom/dumpsoftwareversions/main.nf} | 30 ++++--- .../custom/dumpsoftwareversions/meta.yml | 33 ++++++++ .../pipeline-template/workflows/pipeline.nf | 10 +-- 6 files changed, 140 insertions(+), 17 deletions(-) create mode 100644 nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/functions.nf rename nf_core/pipeline-template/modules/{local/get_software_versions.nf => nf-core/modules/custom/dumpsoftwareversions/main.nf} (75%) create mode 100644 nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml diff --git a/nf_core/pipeline-template/conf/base.config b/nf_core/pipeline-template/conf/base.config index 3fb7b48fca..e0557b9e91 100644 --- a/nf_core/pipeline-template/conf/base.config +++ b/nf_core/pipeline-template/conf/base.config @@ -54,4 +54,7 @@ process { errorStrategy = 'retry' maxRetries = 2 } + withName:CUSTOM_DUMPSOFTWAREVERSIONS { + cache = false + } } diff --git a/nf_core/pipeline-template/modules.json b/nf_core/pipeline-template/modules.json index 5dd4e438d9..e02180274b 100644 --- a/nf_core/pipeline-template/modules.json +++ b/nf_core/pipeline-template/modules.json @@ -3,6 +3,9 @@ "homePage": "https://github.com/{{ name }}", "repos": { "nf-core/modules": { + "custom/dumpsoftwareversions": { + "git_sha": "b2c2d4deb456d92e21777985bb2eda59002748cc" + }, "fastqc": { "git_sha": "ab67a1d41b63bf52fd7c147f7f8f6e8d167590b5" }, diff --git a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/functions.nf new file mode 100644 index 0000000000..85628ee0eb --- /dev/null +++ b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/functions.nf @@ -0,0 +1,78 @@ +// +// 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() +} + +// +// Extract name of module from process name using $task.process +// +def getProcessName(task_process) { + return task_process.tokenize(':')[-1] +} + +// +// 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) { + def ioptions = initOptions(args.options) + def path_list = [ ioptions.publish_dir ?: args.publish_dir ] + + // Do not publish versions.yml unless running from pytest workflow + if (args.filename.equals('versions.yml') && !System.getenv("NF_CORE_MODULES_TEST")) { + return null + } + 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/nf_core/pipeline-template/modules/local/get_software_versions.nf b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf similarity index 75% rename from nf_core/pipeline-template/modules/local/get_software_versions.nf rename to nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf index 08d58f9c52..79e60cb272 100644 --- a/nf_core/pipeline-template/modules/local/get_software_versions.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf @@ -1,35 +1,37 @@ // Import generic module functions -include { saveFiles } from './functions' +include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' params.options = [:] +options = initOptions(params.options) -process GET_SOFTWARE_VERSIONS { +process CUSTOM_DUMPSOFTWAREVERSIONS { + label 'process_low' publishDir "${params.outdir}", mode: params.publish_dir_mode, saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:'pipeline_info', meta:[:], publish_by_meta:[]) } - // This module only requires the PyYAML library, but rather than create a new container on biocontainers we reuse the multiqc container. - conda (params.enable_conda ? "bioconda::multiqc=1.10.1" : null) + // Requires `pyyaml` which does not have a dedicated container but is in the MultiQC container + conda (params.enable_conda ? "bioconda::multiqc=1.11" : null) if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) { - container "https://depot.galaxyproject.org/singularity/multiqc:1.10.1--pyhdfd78af_1" + container "https://depot.galaxyproject.org/singularity/multiqc:1.11--pyhdfd78af_0" } else { - container "quay.io/biocontainers/multiqc:1.10.1--pyhdfd78af_1" + container "quay.io/biocontainers/multiqc:1.11--pyhdfd78af_0" } - cache false - input: path versions output: - path "software_versions.yml" , emit: yml - path "software_versions_mqc.yml" , emit: mqc_yml + path 'software_versions.yml' , emit: yml + path 'software_versions_mqc.yml', emit: mqc_yaml + path 'versions.yml' , emit: versions script: """ #!/usr/bin/env python import yaml + import platform from textwrap import dedent def _make_versions_html(versions): @@ -91,5 +93,13 @@ process GET_SOFTWARE_VERSIONS { yaml.dump(versions, f, default_flow_style=False) with open("software_versions_mqc.yml", 'w') as f: yaml.dump(versions_mqc, f, default_flow_style=False) + + yaml_version = {} + yaml_version["${getProcessName(task.process)}"] = { + 'python': platform.python_version(), + 'yaml': yaml.__version__ + } + with open('versions.yml', 'w') as f: + yaml.dump(yaml_version, f, default_flow_style=False) """ } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml new file mode 100644 index 0000000000..1cf616159c --- /dev/null +++ b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml @@ -0,0 +1,33 @@ +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 + +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" + - version: + type: file + description: File containing software version + pattern: "versions.yml" + +authors: + - "@drpatelh" diff --git a/nf_core/pipeline-template/workflows/pipeline.nf b/nf_core/pipeline-template/workflows/pipeline.nf index 44924b8116..4239f20f50 100644 --- a/nf_core/pipeline-template/workflows/pipeline.nf +++ b/nf_core/pipeline-template/workflows/pipeline.nf @@ -35,11 +35,6 @@ 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 : ['yml':'']] ) - // // SUBWORKFLOW: Consisting of a mix of local and nf-core/modules // @@ -59,6 +54,7 @@ multiqc_options.args += params.multiqc_title ? Utils.joinModuleArgs(["--title \" // 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 { CUSTOM_DUMPSOFTWAREVERSIONS } from '../modules/nf-core/modules/custom/dumpsoftwareversions/main' addParams( options: [publish_files : ['_versions.yml':'']] ) /* ======================================================================================== @@ -88,7 +84,7 @@ workflow {{ short_name|upper }} { ) ch_software_versions = ch_software_versions.mix(FASTQC.out.version.first().ifEmpty(null)) - GET_SOFTWARE_VERSIONS ( + CUSTOM_DUMPSOFTWAREVERSIONS ( ch_software_versions.collectFile() ) @@ -102,7 +98,7 @@ workflow {{ short_name|upper }} { 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.mqc_yml.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 ( From ebac98ff61e35d84a1752a1104616c55696b1c40 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Wed, 29 Sep 2021 16:45:26 +0100 Subject: [PATCH 088/266] Fix tyop in custom/dumpsoftwareversions --- nf_core/pipeline-template/modules.json | 2 +- .../modules/nf-core/modules/custom/dumpsoftwareversions/main.nf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/pipeline-template/modules.json b/nf_core/pipeline-template/modules.json index e02180274b..9b6539650b 100644 --- a/nf_core/pipeline-template/modules.json +++ b/nf_core/pipeline-template/modules.json @@ -4,7 +4,7 @@ "repos": { "nf-core/modules": { "custom/dumpsoftwareversions": { - "git_sha": "b2c2d4deb456d92e21777985bb2eda59002748cc" + "git_sha": "5a757b2981b634b94015da5969931b96a9f6b8da" }, "fastqc": { "git_sha": "ab67a1d41b63bf52fd7c147f7f8f6e8d167590b5" diff --git a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf index 79e60cb272..94e112f09f 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf @@ -23,7 +23,7 @@ process CUSTOM_DUMPSOFTWAREVERSIONS { output: path 'software_versions.yml' , emit: yml - path 'software_versions_mqc.yml', emit: mqc_yaml + path 'software_versions_mqc.yml', emit: mqc_yml path 'versions.yml' , emit: versions script: From cc03754256cfdb482e24829e1b2b8bf6c3645ab2 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Wed, 29 Sep 2021 16:51:00 +0100 Subject: [PATCH 089/266] Remove requirement for get_software_versions.py --- nf_core/lint/files_exist.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nf_core/lint/files_exist.py b/nf_core/lint/files_exist.py index e1b28bc9a3..41365b1094 100644 --- a/nf_core/lint/files_exist.py +++ b/nf_core/lint/files_exist.py @@ -51,7 +51,6 @@ def files_exist(self): lib/NfcoreTemplate.groovy lib/Utils.groovy lib/WorkflowMain.groovy - modules/local/get_software_versions.nf nextflow_schema.json nextflow.config README.md @@ -133,7 +132,6 @@ def files_exist(self): [os.path.join("lib", "NfcoreTemplate.groovy")], [os.path.join("lib", "Utils.groovy")], [os.path.join("lib", "WorkflowMain.groovy")], - [os.path.join("modules", "local", "get_software_versions.nf")], ] files_warn = [ From 5c8d292e23752195c98ec34bfdeb4931baeefc9d Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Wed, 29 Sep 2021 16:59:55 +0100 Subject: [PATCH 090/266] Fix module lint test --- tests/modules/lint.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/modules/lint.py b/tests/modules/lint.py index 8371b92fb7..0d25f62880 100644 --- a/tests/modules/lint.py +++ b/tests/modules/lint.py @@ -15,6 +15,7 @@ def test_modules_lint_empty(self): """Test linting a pipeline with no modules installed""" self.mods_remove.remove("fastqc") self.mods_remove.remove("multiqc") + self.mods_remove.remove("custom/dumpsoftwareversions") module_lint = nf_core.modules.ModuleLint(dir=self.pipeline_dir) module_lint.lint(print_results=False, all_modules=True) assert len(module_lint.passed) == 0 From 2485384624112db989d234b0128c4de97c9f7aa2 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 30 Sep 2021 09:03:19 +0100 Subject: [PATCH 091/266] Dump version for /custom/dumpsoftwareversions module itself --- nf_core/pipeline-template/modules.json | 2 +- .../custom/dumpsoftwareversions/main.nf | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/nf_core/pipeline-template/modules.json b/nf_core/pipeline-template/modules.json index 9b6539650b..c828028317 100644 --- a/nf_core/pipeline-template/modules.json +++ b/nf_core/pipeline-template/modules.json @@ -4,7 +4,7 @@ "repos": { "nf-core/modules": { "custom/dumpsoftwareversions": { - "git_sha": "5a757b2981b634b94015da5969931b96a9f6b8da" + "git_sha": "22ec5c6007159d441585ef54bfa6272b6f93c78a" }, "fastqc": { "git_sha": "ab67a1d41b63bf52fd7c147f7f8f6e8d167590b5" diff --git a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf index 94e112f09f..8424ab07b8 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf @@ -72,10 +72,16 @@ process CUSTOM_DUMPSOFTWAREVERSIONS { html.append("") return "\\n".join(html) + module_versions = {} + module_versions["${getProcessName(task.process)}"] = { + 'python': platform.python_version(), + 'yaml': yaml.__version__ + } + with open("$versions") as f: - versions = yaml.safe_load(f) + workflow_versions = yaml.safe_load(f) | module_versions - versions["Workflow"] = { + workflow_versions["Workflow"] = { "Nextflow": "$workflow.nextflow.version", "$workflow.manifest.name": "$workflow.manifest.version" } @@ -86,20 +92,15 @@ process CUSTOM_DUMPSOFTWAREVERSIONS { '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) + 'data': _make_versions_html(workflow_versions) } with open("software_versions.yml", 'w') as f: - yaml.dump(versions, f, default_flow_style=False) + yaml.dump(workflow_versions, f, default_flow_style=False) with open("software_versions_mqc.yml", 'w') as f: yaml.dump(versions_mqc, f, default_flow_style=False) - yaml_version = {} - yaml_version["${getProcessName(task.process)}"] = { - 'python': platform.python_version(), - 'yaml': yaml.__version__ - } with open('versions.yml', 'w') as f: - yaml.dump(yaml_version, f, default_flow_style=False) + yaml.dump(module_versions, f, default_flow_style=False) """ } From 07d51d9d8c1b7144446ecadd9d327b90e27fd5c7 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Fri, 1 Oct 2021 14:24:55 +0100 Subject: [PATCH 092/266] Rename version to versions in meta.yml --- nf_core/module-template/modules/meta.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/module-template/modules/meta.yml b/nf_core/module-template/modules/meta.yml index c42dd613bd..d58df5a371 100644 --- a/nf_core/module-template/modules/meta.yml +++ b/nf_core/module-template/modules/meta.yml @@ -37,9 +37,9 @@ output: Groovy Map containing sample information e.g. [ id:'test', single_end:false ] {% endif -%} - - version: + - versions: type: file - description: File containing software version + description: File containing software versions pattern: "versions.yml" ## TODO nf-core: Delete / customise this example output - bam: From 2a7da3aa5bb0f03383161c3e8c89bd32f9c6784f Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Fri, 1 Oct 2021 14:32:45 +0100 Subject: [PATCH 093/266] Update modules in pipeline template with new versions syntax --- nf_core/module-template/modules/main.nf | 6 +++--- nf_core/pipeline-template/modules.json | 6 +++--- .../modules/local/samplesheet_check.nf | 8 +++++++- .../modules/custom/dumpsoftwareversions/main.nf | 6 +++--- .../modules/nf-core/modules/fastqc/main.nf | 8 ++++---- .../modules/nf-core/modules/multiqc/main.nf | 5 ++--- .../subworkflows/local/input_check.nf | 3 ++- nf_core/pipeline-template/workflows/pipeline.nf | 11 ++++++----- 8 files changed, 30 insertions(+), 23 deletions(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 457f2b39bc..26703aeb57 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -52,11 +52,11 @@ process {{ tool_name_underscore|upper }} { // TODO nf-core: Named file extensions MUST be emitted for ALL output channels {{ 'tuple val(meta), path("*.bam")' if has_meta else 'path "*.bam"' }}, emit: bam // TODO nf-core: List additional required output channels/values here - path "versions.yml" , emit: version + path "versions.yml" , emit: versions script: {% if has_meta -%} - def prefix = options.suffix ? "${meta.id}${options.suffix}" : "${meta.id}" + def prefix = options.suffix ? "${meta.id}${options.suffix}" : "${meta.id}" {%- endif %} // TODO nf-core: Where possible, a command MUST be provided to obtain the version number of the software e.g. 1.10 // If the software is unable to output a version number on the command-line then it can be manually specified @@ -80,7 +80,7 @@ process {{ tool_name_underscore|upper }} { cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - samtools: \$( samtools --version 2>&1 | sed 's/^.*samtools //; s/Using.*\$//' ) + ${getSoftwareName(task.process)}: \$( samtools --version 2>&1 | sed 's/^.*samtools //; s/Using.*\$//' ) END_VERSIONS """ } diff --git a/nf_core/pipeline-template/modules.json b/nf_core/pipeline-template/modules.json index c828028317..a225b686a8 100644 --- a/nf_core/pipeline-template/modules.json +++ b/nf_core/pipeline-template/modules.json @@ -4,13 +4,13 @@ "repos": { "nf-core/modules": { "custom/dumpsoftwareversions": { - "git_sha": "22ec5c6007159d441585ef54bfa6272b6f93c78a" + "git_sha": "7b3315591a149609e27914965f858c9a7e071564" }, "fastqc": { - "git_sha": "ab67a1d41b63bf52fd7c147f7f8f6e8d167590b5" + "git_sha": "7b3315591a149609e27914965f858c9a7e071564" }, "multiqc": { - "git_sha": "ab67a1d41b63bf52fd7c147f7f8f6e8d167590b5" + "git_sha": "7b3315591a149609e27914965f858c9a7e071564" } } } diff --git a/nf_core/pipeline-template/modules/local/samplesheet_check.nf b/nf_core/pipeline-template/modules/local/samplesheet_check.nf index 9bada69b98..fbb4fc1101 100644 --- a/nf_core/pipeline-template/modules/local/samplesheet_check.nf +++ b/nf_core/pipeline-template/modules/local/samplesheet_check.nf @@ -20,12 +20,18 @@ process SAMPLESHEET_CHECK { path samplesheet output: - path '*.csv' + path '*.csv' , emit: csv + path "versions.yml", emit: versions script: // This script is bundled with the pipeline, in {{ name }}/bin/ """ check_samplesheet.py \\ $samplesheet \\ samplesheet.valid.csv + + cat <<-END_VERSIONS > versions.yml + ${getProcessName(task.process)}: + python: \$(python --version | sed 's/Python //g') + END_VERSIONS """ } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf index 8424ab07b8..cf10a8e072 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf @@ -22,9 +22,9 @@ process CUSTOM_DUMPSOFTWAREVERSIONS { path versions output: - path 'software_versions.yml' , emit: yml - path 'software_versions_mqc.yml', emit: mqc_yml - path 'versions.yml' , emit: versions + path "software_versions.yml" , emit: yml + path "software_versions_mqc.yml", emit: mqc_yml + path "versions.yml" , emit: versions script: """ diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index 88bfbf5b02..9f6cfc5538 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -24,11 +24,11 @@ process FASTQC { output: tuple val(meta), path("*.html"), emit: html tuple val(meta), path("*.zip") , emit: zip - path "versions.yml" , emit: version + path "versions.yml" , emit: versions script: // Add soft-links to original FastQs for consistent naming in pipeline - def prefix = options.suffix ? "${meta.id}${options.suffix}" : "${meta.id}" + def prefix = options.suffix ? "${meta.id}${options.suffix}" : "${meta.id}" if (meta.single_end) { """ [ ! -f ${prefix}.fastq.gz ] && ln -s $reads ${prefix}.fastq.gz @@ -36,7 +36,7 @@ process FASTQC { cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - fastqc: \$( fastqc --version | sed -e "s/FastQC v//g" ) + ${getSoftwareName(task.process)}: \$( fastqc --version | sed -e "s/FastQC v//g" ) END_VERSIONS """ } else { @@ -47,7 +47,7 @@ process FASTQC { cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - fastqc: \$( fastqc --version | sed -e "s/FastQC v//g" ) + ${getSoftwareName(task.process)}: \$( fastqc --version | sed -e "s/FastQC v//g" ) END_VERSIONS """ } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index 2e7ad932e5..0861aa5934 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -24,16 +24,15 @@ process MULTIQC { path "*multiqc_report.html", emit: report path "*_data" , emit: data path "*_plots" , optional:true, emit: plots - path "versions.yml" , emit: version + path "versions.yml" , emit: versions script: - def software = getSoftwareName(task.process) """ multiqc -f $options.args . cat <<-END_VERSIONS > versions.yml ${getProcessName(task.process)}: - multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) + ${getSoftwareName(task.process)}: \$( multiqc --version | sed -e "s/multiqc, version //g" ) END_VERSIONS """ } diff --git a/nf_core/pipeline-template/subworkflows/local/input_check.nf b/nf_core/pipeline-template/subworkflows/local/input_check.nf index b664bc8caf..6c23c8f16f 100644 --- a/nf_core/pipeline-template/subworkflows/local/input_check.nf +++ b/nf_core/pipeline-template/subworkflows/local/input_check.nf @@ -17,7 +17,8 @@ workflow INPUT_CHECK { .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/nf_core/pipeline-template/workflows/pipeline.nf b/nf_core/pipeline-template/workflows/pipeline.nf index 4239f20f50..0682ed96cc 100644 --- a/nf_core/pipeline-template/workflows/pipeline.nf +++ b/nf_core/pipeline-template/workflows/pipeline.nf @@ -67,7 +67,7 @@ def multiqc_report = [] workflow {{ short_name|upper }} { - ch_software_versions = Channel.empty() + ch_versions = Channel.empty() // // SUBWORKFLOW: Read in samplesheet, validate and stage input files @@ -75,6 +75,7 @@ workflow {{ short_name|upper }} { INPUT_CHECK ( ch_input ) + ch_versions = ch_versions.mix(INPUT_CHECK.out.versions) // // MODULE: Run FastQC @@ -82,10 +83,10 @@ workflow {{ short_name|upper }} { 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()) CUSTOM_DUMPSOFTWAREVERSIONS ( - ch_software_versions.collectFile() + ch_versions.collectFile() ) // @@ -104,8 +105,8 @@ workflow {{ short_name|upper }} { 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) } /* From 575350bb976128104a251c109d44317ede935fb6 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Fri, 1 Oct 2021 14:48:42 +0100 Subject: [PATCH 094/266] Fix channel bug --- nf_core/pipeline-template/subworkflows/local/input_check.nf | 1 + 1 file changed, 1 insertion(+) diff --git a/nf_core/pipeline-template/subworkflows/local/input_check.nf b/nf_core/pipeline-template/subworkflows/local/input_check.nf index 6c23c8f16f..c1b071961c 100644 --- a/nf_core/pipeline-template/subworkflows/local/input_check.nf +++ b/nf_core/pipeline-template/subworkflows/local/input_check.nf @@ -12,6 +12,7 @@ workflow INPUT_CHECK { main: SAMPLESHEET_CHECK ( samplesheet ) + .csv .splitCsv ( header:true, sep:',' ) .map { create_fastq_channels(it) } .set { reads } From a56dca36049b0b61c2a6ca5d517561051b4e1193 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Fri, 1 Oct 2021 16:09:47 +0200 Subject: [PATCH 095/266] Include getProcessName --- nf_core/pipeline-template/modules/local/samplesheet_check.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/modules/local/samplesheet_check.nf b/nf_core/pipeline-template/modules/local/samplesheet_check.nf index fbb4fc1101..b8354f35e4 100644 --- a/nf_core/pipeline-template/modules/local/samplesheet_check.nf +++ b/nf_core/pipeline-template/modules/local/samplesheet_check.nf @@ -1,5 +1,5 @@ // Import generic module functions -include { saveFiles } from './functions' +include { saveFiles; getProcessName } from './functions' params.options = [:] From b402201076a28094b9a70a654897e4b5e842b34d Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Wed, 6 Oct 2021 22:51:43 +0200 Subject: [PATCH 096/266] Update template to fix dumpsoftwareversions --- nf_core/pipeline-template/modules.json | 2 +- .../nf-core/modules/custom/dumpsoftwareversions/main.nf | 2 +- .../nf-core/modules/custom/dumpsoftwareversions/meta.yml | 4 ++-- nf_core/pipeline-template/workflows/pipeline.nf | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nf_core/pipeline-template/modules.json b/nf_core/pipeline-template/modules.json index a225b686a8..db6aceae73 100644 --- a/nf_core/pipeline-template/modules.json +++ b/nf_core/pipeline-template/modules.json @@ -4,7 +4,7 @@ "repos": { "nf-core/modules": { "custom/dumpsoftwareversions": { - "git_sha": "7b3315591a149609e27914965f858c9a7e071564" + "git_sha": "84f2302920078b0cf7716b2a2e5fcc0be5c4531d" }, "fastqc": { "git_sha": "7b3315591a149609e27914965f858c9a7e071564" diff --git a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf index cf10a8e072..faf2073f75 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf @@ -79,7 +79,7 @@ process CUSTOM_DUMPSOFTWAREVERSIONS { } with open("$versions") as f: - workflow_versions = yaml.safe_load(f) | module_versions + workflow_versions = yaml.load(f, Loader=yaml.BaseLoader) | module_versions workflow_versions["Workflow"] = { "Nextflow": "$workflow.nextflow.version", diff --git a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml index 1cf616159c..8d4a6ed42c 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml +++ b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml @@ -24,9 +24,9 @@ output: type: file description: MultiQC custom content YML file containing software versions pattern: "software_versions_mqc.yml" - - version: + - versions: type: file - description: File containing software version + description: File containing software versions pattern: "versions.yml" authors: diff --git a/nf_core/pipeline-template/workflows/pipeline.nf b/nf_core/pipeline-template/workflows/pipeline.nf index 0682ed96cc..68ca75e1de 100644 --- a/nf_core/pipeline-template/workflows/pipeline.nf +++ b/nf_core/pipeline-template/workflows/pipeline.nf @@ -86,7 +86,7 @@ workflow {{ short_name|upper }} { ch_versions = ch_versions.mix(FASTQC.out.versions.first()) CUSTOM_DUMPSOFTWAREVERSIONS ( - ch_versions.collectFile() + ch_versions.unique().collectFile(name: 'collated_versions.yml') ) // From 56162a52dad0a73169f706521855caa985b2ce3d Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Wed, 6 Oct 2021 22:58:24 +0200 Subject: [PATCH 097/266] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc696843c9..c11ad5dab0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Template * Modify software version channel handling to support multiple software version emissions (e.g. from mulled containers), and multiple software versions. +* Update `dumpsoftwareversion` module to correctly report versions with trailing zeros. ### General From fd6486454160acc8bc27be9626c2146eca613b28 Mon Sep 17 00:00:00 2001 From: "Moritz E. Beber" Date: Fri, 15 Oct 2021 23:08:54 +0200 Subject: [PATCH 098/266] fix: update build system --- MANIFEST.in | 1 + pyproject.toml | 7 +++++++ setup.py | 1 - 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 59d5a1a4e0..1f03217204 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include LICENSE include README.md recursive-include nf_core * +include requirements.txt \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 266acbdcb6..b0055a4e00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,10 @@ +[build-system] +build-backend = 'setuptools.build_meta' +requires = [ + 'setuptools>=40.6.0', + 'wheel' +] + [tool.black] line-length = 120 target_version = ['py36','py37','py38'] diff --git a/setup.py b/setup.py index 513f9d9fec..fc2ee983ec 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,6 @@ license="MIT", entry_points={"console_scripts": ["nf-core=nf_core.__main__:run_nf_core"]}, install_requires=required, - setup_requires=["twine>=1.11.0", "setuptools>=38.6."], packages=find_packages(exclude=("docs")), include_package_data=True, zip_safe=False, From 3177bd4ce15040f4f415787a5c2e0c1aad667cf0 Mon Sep 17 00:00:00 2001 From: "Moritz E. Beber" Date: Fri, 15 Oct 2021 23:24:24 +0200 Subject: [PATCH 099/266] chore: explicitly include only template directories --- MANIFEST.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 1f03217204..bb3c5d6dac 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include LICENSE include README.md -recursive-include nf_core * +graft nf_core/module-template +graft nf_core/pipeline-template include requirements.txt \ No newline at end of file From 4e8a065eea4859345fb5d6e531cf4bfd2cf42591 Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Fri, 22 Oct 2021 15:27:13 +0200 Subject: [PATCH 100/266] authentication using github_api_auto_auth Passing auth parameters with requests.get Fix sha query param in get_module_git_log to get log from private branches --- nf_core/modules/module_utils.py | 6 +++--- nf_core/modules/modules_repo.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nf_core/modules/module_utils.py b/nf_core/modules/module_utils.py index 30d6e4cc08..7c11e0a5d3 100644 --- a/nf_core/modules/module_utils.py +++ b/nf_core/modules/module_utils.py @@ -57,7 +57,7 @@ def get_module_git_log(module_name, modules_repo=None, per_page=30, page_nbr=1, if modules_repo is None: modules_repo = ModulesRepo() api_url = f"https://api.github.com/repos/{modules_repo.name}/commits" - api_url += f"?sha{modules_repo.branch}" + api_url += f"?sha={modules_repo.branch}" if module_name is not None: api_url += f"&path=modules/{module_name}" api_url += f"&page={page_nbr}" @@ -259,7 +259,7 @@ def local_module_equal_to_commit(local_files, module_name, modules_repo, commit_ for i, file in enumerate(files_to_check): # Download remote copy and compare api_url = f"{module_base_url}/{file}" - r = requests.get(url=api_url) + r = requests.get(url=api_url, auth=nf_core.utils.github_api_auto_auth()) if r.status_code != 200: log.debug(f"Could not download remote copy of file module {module_name}/{file}") log.debug(api_url) @@ -365,7 +365,7 @@ def verify_pipeline_dir(dir): modules_is_software = False for repo_name in repo_names: api_url = f"https://api.github.com/repos/{repo_name}/contents" - response = requests.get(api_url) + response = requests.get(api_url, auth=nf_core.utils.github_api_auto_auth()) if response.status_code == 404: missing_remote.append(repo_name) if repo_name == "nf-core/software": diff --git a/nf_core/modules/modules_repo.py b/nf_core/modules/modules_repo.py index d6d2871ecd..9a9c4f032c 100644 --- a/nf_core/modules/modules_repo.py +++ b/nf_core/modules/modules_repo.py @@ -39,7 +39,7 @@ def verify_modules_repo(self): # Check if repository exist api_url = f"https://api.github.com/repos/{self.name}/branches" - response = requests.get(api_url) + response = requests.get(api_url, auth=nf_core.utils.github_api_auto_auth()) if response.status_code == 200: branches = [branch["name"] for branch in response.json()] if self.branch not in branches: @@ -48,7 +48,7 @@ def verify_modules_repo(self): raise LookupError(f"Repository '{self.name}' is not available on GitHub") api_url = f"https://api.github.com/repos/{self.name}/contents?ref={self.branch}" - response = requests.get(api_url) + response = requests.get(api_url, auth=nf_core.utils.github_api_auto_auth()) if response.status_code == 200: dir_names = [entry["name"] for entry in response.json() if entry["type"] == "dir"] if "modules" not in dir_names: From 22fe40291233c692b68c31f2c7c54ca9eece3758 Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Fri, 22 Oct 2021 16:20:51 +0200 Subject: [PATCH 101/266] list modules from private repository passing repo_name information while searching for local modules info --- nf_core/modules/list.py | 3 ++- nf_core/modules/module_utils.py | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/nf_core/modules/list.py b/nf_core/modules/list.py index 355b952c4c..743670908d 100644 --- a/nf_core/modules/list.py +++ b/nf_core/modules/list.py @@ -103,7 +103,8 @@ def pattern_msg(keywords): if module_entry: version_sha = module_entry["git_sha"] try: - message, date = nf_core.modules.module_utils.get_commit_info(version_sha) + # pass repo_name to get info on modules even outside nf-core/modules + message, date = nf_core.modules.module_utils.get_commit_info(version_sha, repo_name) except LookupError as e: log.warning(e) date = "[red]Not Available" diff --git a/nf_core/modules/module_utils.py b/nf_core/modules/module_utils.py index 7c11e0a5d3..3e791b3ecc 100644 --- a/nf_core/modules/module_utils.py +++ b/nf_core/modules/module_utils.py @@ -46,6 +46,7 @@ def get_module_git_log(module_name, modules_repo=None, per_page=30, page_nbr=1, update breaking backwards compatibility. Args: module_name (str): Name of module + modules_repo (ModulesRepo): A ModulesRepo object configured for the repository in question per_page (int): Number of commits per page returned by API page_nbr (int): Page number of the retrieved commits since (str): Only show commits later than this timestamp. @@ -84,19 +85,21 @@ def get_module_git_log(module_name, modules_repo=None, per_page=30, page_nbr=1, ) -def get_commit_info(commit_sha): +def get_commit_info(commit_sha, repo_name="nf-core/modules"): """ Fetches metadata about the commit (dates, message, etc.) Args: - module_name (str): Name of module commit_sha (str): The SHA of the requested commit + repo_name (str): module repos name (def. {0}) Returns: message (str): The commit message for the requested commit date (str): The commit date for the requested commit Raises: LookupError: If the call to the API fails. - """ - api_url = f"https://api.github.com/repos/nf-core/modules/commits/{commit_sha}?stats=false" + """.format( + repo_name + ) + api_url = f"https://api.github.com/repos/{repo_name}/commits/{commit_sha}?stats=false" log.debug(f"Fetching commit metadata for commit at {commit_sha}") response = requests.get(api_url, auth=nf_core.utils.github_api_auto_auth()) if response.status_code == 200: From 2c8e1993fcbb11ed4d93c3b35a78de93c0293465 Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Fri, 22 Oct 2021 17:25:59 +0200 Subject: [PATCH 102/266] solve DepreactionWarning in utils suppress 'DeprecationWarning: invalid escape sequence \.' by using patterns explicitely --- nf_core/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/utils.py b/nf_core/utils.py index de120f10d7..90935e4083 100644 --- a/nf_core/utils.py +++ b/nf_core/utils.py @@ -58,14 +58,14 @@ def check_if_outdated(current_version=None, remote_version=None, source_url="htt # Set and clean up the current version string if current_version == None: current_version = nf_core.__version__ - current_version = re.sub("[^0-9\.]", "", current_version) + current_version = re.sub(r"[^0-9\.]", "", current_version) # Build the URL to check against source_url = os.environ.get("NFCORE_VERSION_URL", source_url) source_url = "{}?v={}".format(source_url, current_version) # Fetch and clean up the remote version if remote_version == None: response = requests.get(source_url, timeout=3) - remote_version = re.sub("[^0-9\.]", "", response.text) + remote_version = re.sub(r"[^0-9\.]", "", response.text) # Check if we have an available update is_outdated = version.StrictVersion(remote_version) > version.StrictVersion(current_version) return (is_outdated, current_version, remote_version) From b60a7f24ecac9645051d5f84eafbd9497eac652c Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Fri, 22 Oct 2021 17:40:03 +0200 Subject: [PATCH 103/266] fix test_wf_use_local_configs after first attempt tests/test_download.py::DownloadTest::test_wf_use_local_configs was failing forever after first attempt since the first attempt cached a configuration file which is reloaded in all the following cases. Now we disable caching in $HOME/.nextflow/nf-core while during tests --- nf_core/download.py | 2 ++ nf_core/utils.py | 11 +++++++++-- tests/test_download.py | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/nf_core/download.py b/nf_core/download.py index bb83564953..7f03459cf6 100644 --- a/nf_core/download.py +++ b/nf_core/download.py @@ -396,6 +396,7 @@ def wf_use_local_configs(self): nfconfig = nfconfig_fh.read() # Replace the target string + log.debug(f"Replacing '{find_str}' with '{repl_str}'") nfconfig = nfconfig.replace(find_str, repl_str) # Append the singularity.cacheDir to the end if we need it @@ -407,6 +408,7 @@ def wf_use_local_configs(self): ) # Write the file out again + log.debug(f"Updating '{nfconfig_fn}'") with open(nfconfig_fn, "w") as nfconfig_fh: nfconfig_fh.write(nfconfig) diff --git a/nf_core/utils.py b/nf_core/utils.py index 90935e4083..55751ae341 100644 --- a/nf_core/utils.py +++ b/nf_core/utils.py @@ -200,17 +200,20 @@ def is_pipeline_directory(wf_path): raise UserWarning(f"'{wf_path}' is not a pipeline - '{fn}' is missing") -def fetch_wf_config(wf_path): +def fetch_wf_config(wf_path, cache_config=True): """Uses Nextflow to retrieve the the configuration variables from a Nextflow workflow. Args: wf_path (str): Nextflow workflow file system path. + cache_config (bool): cache configuration or not (def. True) Returns: dict: Workflow configuration settings. """ + log.debug(f"Got '{wf_path}' as path") + config = dict() cache_fn = None cache_basedir = None @@ -272,7 +275,11 @@ def fetch_wf_config(wf_path): log.debug("Could not open {} to look for parameter declarations - {}".format(main_nf, e)) # If we can, save a cached copy - if cache_path: + # HINT: during testing phase (in test_download, for example) we don't want + # to save configuration copy in $HOME, otherwise the tests/test_download.py::DownloadTest::test_wf_use_local_configs + # will fail after the first attempt. It's better to not save temporary data + # in others folders than tmp when doing tests in general + if cache_path and cache_config: log.debug("Saving config cache: {}".format(cache_path)) with open(cache_path, "w") as fh: json.dump(config, fh, indent=4) diff --git a/tests/test_download.py b/tests/test_download.py index a4ae8e205d..7025cd5ad5 100644 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -112,7 +112,7 @@ def test_wf_use_local_configs(self): # Test the function download_obj.wf_use_local_configs() - wf_config = nf_core.utils.fetch_wf_config(os.path.join(test_outdir, "workflow")) + wf_config = nf_core.utils.fetch_wf_config(os.path.join(test_outdir, "workflow"), cache_config=False) assert wf_config["params.custom_config_base"] == f"'{test_outdir}/workflow/../configs/'" # From d07653bf88c9ba43615b6157120adbca7fbb9b61 Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Fri, 22 Oct 2021 18:03:03 +0200 Subject: [PATCH 104/266] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c11ad5dab0..57b8cc2ed6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Template +* Disable cache in `nf_core.utils.fetch_wf_config` while performing `test_wf_use_local_configs`. * Modify software version channel handling to support multiple software version emissions (e.g. from mulled containers), and multiple software versions. * Update `dumpsoftwareversion` module to correctly report versions with trailing zeros. From 3f2581d81312d6c3e9b2debd460dc9d83687e4c1 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 26 Oct 2021 14:27:58 +0200 Subject: [PATCH 105/266] Fixed bug in `nf-core list` when `NXF_HOME` is set --- CHANGELOG.md | 1 + nf_core/list.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c11ad5dab0..4ca9808900 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * Fixed bug in `nf-core launch` due to revisions specified with `-r` not being added to nextflow command. ([#1246](https://github.com/nf-core/tools/issues/1246)) * Update regex in `readme` test of `nf-core lint` to agree with the pipeline template ([#1260](https://github.com/nf-core/tools/issues/1260)) * Update 'fix' message in `nf-core lint` to conform to the current command line options. ([#1259](https://github.com/nf-core/tools/issues/1259)) +* Fixed bug in `nf-core list` when `NXF_HOME` is set ### Modules diff --git a/nf_core/list.py b/nf_core/list.py index 4d8b31f079..5a94253d00 100644 --- a/nf_core/list.py +++ b/nf_core/list.py @@ -334,7 +334,7 @@ def get_local_nf_workflow_details(self): if len(os.environ.get("NXF_ASSETS", "")) > 0: nf_wfdir = os.path.join(os.environ.get("NXF_ASSETS"), self.full_name) elif len(os.environ.get("NXF_HOME", "")) > 0: - nf_wfdir = os.path.join(os.environ.get("NXF_HOME"), "assets") + nf_wfdir = os.path.join(os.environ.get("NXF_HOME"), "assets", self.full_name) else: nf_wfdir = os.path.join(os.getenv("HOME"), ".nextflow", "assets", self.full_name) if os.path.isdir(nf_wfdir): From d79cdb68af580246511b055e371dccaeeef1b239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6rtenhuber?= Date: Tue, 26 Oct 2021 16:41:12 +0200 Subject: [PATCH 106/266] change out-dated `/software/` paths to `/modules/ --- nf_core/module-template/modules/main.nf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 6e4dcde636..c4db5d6d89 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -2,7 +2,7 @@ include { initOptions; saveFiles; getSoftwareName } from './functions' // TODO nf-core: If in doubt look at other nf-core/modules to see how we are doing things! :) -// https://github.com/nf-core/modules/tree/master/software +// https://github.com/nf-core/modules/tree/master/modules // You can also ask for help via your pull request or on the #modules channel on the nf-core Slack workspace: // https://nf-co.re/join @@ -43,7 +43,7 @@ process {{ tool_name_underscore|upper }} { // TODO nf-core: Where applicable all sample-specific information e.g. "id", "single_end", "read_group" // MUST be provided as an input via a Groovy Map called "meta". // This information may not be required in some instances e.g. indexing reference genome files: - // https://github.com/nf-core/modules/blob/master/software/bwa/index/main.nf + // https://github.com/nf-core/modules/blob/master/modules/bwa/index/main.nf // TODO nf-core: Where applicable please provide/convert compressed files as input/output // e.g. "*.fastq.gz" and NOT "*.fastq", "*.bam" and NOT "*.sam" etc. {{ 'tuple val(meta), path(bam)' if has_meta else 'path bam' }} @@ -61,7 +61,7 @@ process {{ tool_name_underscore|upper }} { {%- endif %} // TODO nf-core: Where possible, a command MUST be provided to obtain the version number of the software e.g. 1.10 // If the software is unable to output a version number on the command-line then it can be manually specified - // e.g. https://github.com/nf-core/modules/blob/master/software/homer/annotatepeaks/main.nf + // e.g. https://github.com/nf-core/modules/blob/master/modules/homer/annotatepeaks/main.nf // TODO nf-core: It MUST be possible to pass additional parameters to the tool as a command-line string via the "$options.args" variable // TODO nf-core: If the tool supports multi-threading then you MUST provide the appropriate parameter // using the Nextflow "task" variable e.g. "--threads $task.cpus" From 0aff2a9f1ce66c6fb8f43bbee25c9311c907af2e Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Tue, 2 Nov 2021 16:39:54 +0100 Subject: [PATCH 107/266] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0ff592c45..d7122a3a28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Template +* Deal with authentication with private repositories * Disable cache in `nf_core.utils.fetch_wf_config` while performing `test_wf_use_local_configs`. * Modify software version channel handling to support multiple software version emissions (e.g. from mulled containers), and multiple software versions. * Update `dumpsoftwareversion` module to correctly report versions with trailing zeros. From bb5f4603e27010f2098179c29b7c17ec725d95f8 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Wed, 3 Nov 2021 11:21:16 +0000 Subject: [PATCH 108/266] Remove params.hostnames from pipeline template --- .../pipeline-template/lib/NfcoreSchema.groovy | 3 +-- .../lib/NfcoreTemplate.groovy | 25 ------------------- .../pipeline-template/lib/WorkflowMain.groovy | 3 --- nf_core/pipeline-template/nextflow.config | 1 - .../pipeline-template/nextflow_schema.json | 6 ----- 5 files changed, 1 insertion(+), 37 deletions(-) diff --git a/nf_core/pipeline-template/lib/NfcoreSchema.groovy b/nf_core/pipeline-template/lib/NfcoreSchema.groovy index 8d6920dd64..dcb39c839e 100755 --- a/nf_core/pipeline-template/lib/NfcoreSchema.groovy +++ b/nf_core/pipeline-template/lib/NfcoreSchema.groovy @@ -260,13 +260,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 diff --git a/nf_core/pipeline-template/lib/NfcoreTemplate.groovy b/nf_core/pipeline-template/lib/NfcoreTemplate.groovy index 44551e0a35..eb7c554070 100755 --- a/nf_core/pipeline-template/lib/NfcoreTemplate.groovy +++ b/nf_core/pipeline-template/lib/NfcoreTemplate.groovy @@ -18,31 +18,6 @@ class NfcoreTemplate { } } - // - // Check params.hostnames - // - 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}." - } - } - } - // // Construct and send completion email // diff --git a/nf_core/pipeline-template/lib/WorkflowMain.groovy b/nf_core/pipeline-template/lib/WorkflowMain.groovy index 597129cb50..db05c0f9ad 100755 --- a/nf_core/pipeline-template/lib/WorkflowMain.groovy +++ b/nf_core/pipeline-template/lib/WorkflowMain.groovy @@ -69,9 +69,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/nf_core/pipeline-template/nextflow.config b/nf_core/pipeline-template/nextflow.config index 3f23f45b34..2be5f632ec 100644 --- a/nf_core/pipeline-template/nextflow.config +++ b/nf_core/pipeline-template/nextflow.config @@ -41,7 +41,6 @@ params { // 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 diff --git a/nf_core/pipeline-template/nextflow_schema.json b/nf_core/pipeline-template/nextflow_schema.json index aacda0004c..c4b03824a8 100644 --- a/nf_core/pipeline-template/nextflow_schema.json +++ b/nf_core/pipeline-template/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.", From 3c7613daff4e807d9b5f6d28eace1dad3d5e3502 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Wed, 3 Nov 2021 11:35:53 +0000 Subject: [PATCH 109/266] Add in CI tests for latest edge version --- .github/workflows/create-test-wf.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/create-test-wf.yml b/.github/workflows/create-test-wf.yml index 16c2f1abf9..c2b9914f6e 100644 --- a/.github/workflows/create-test-wf.yml +++ b/.github/workflows/create-test-wf.yml @@ -1,12 +1,15 @@ name: Create a pipeline and test it on: [push, pull_request] -# Uncomment if we need an edge release of Nextflow again -# env: NXF_EDGE: 1 - jobs: RunTestWorkflow: runs-on: ubuntu-latest + env: + NXF_ANSI_LOG: false + strategy: + matrix: + # Nextflow versions: check pipeline minimum and latest edge version + nxf_ver: ["NXF_VER=21.04.0", "NXF_EDGE=1"] steps: - uses: actions/checkout@v2 name: Check out source-code repository @@ -27,6 +30,8 @@ jobs: run: | wget -qO- get.nextflow.io | bash sudo mv nextflow /usr/local/bin/ + export ${{ matrix.nxf_ver }} + nextflow self-update - name: Run nf-core/tools run: | From c66d15bd6541cf60b955b8b1f2a40bd2ba1371b8 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Wed, 3 Nov 2021 11:40:50 +0000 Subject: [PATCH 110/266] Add in CI tests for latest edge version --- .github/workflows/create-lint-wf.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/create-lint-wf.yml b/.github/workflows/create-lint-wf.yml index cdc354a0e6..dfc062eca7 100644 --- a/.github/workflows/create-lint-wf.yml +++ b/.github/workflows/create-lint-wf.yml @@ -1,12 +1,15 @@ name: Create a pipeline and lint it on: [push, pull_request] -# Uncomment if we need an edge release of Nextflow again -# env: NXF_EDGE: 1 - jobs: MakeTestWorkflow: runs-on: ubuntu-latest + env: + NXF_ANSI_LOG: false + strategy: + matrix: + # Nextflow versions: check pipeline minimum and latest edge version + nxf_ver: ["NXF_VER=21.04.0", "NXF_EDGE=1"] steps: - uses: actions/checkout@v2 name: Check out source-code repository @@ -27,6 +30,8 @@ jobs: run: | wget -qO- get.nextflow.io | bash sudo mv nextflow /usr/local/bin/ + export ${{ matrix.nxf_ver }} + nextflow self-update - name: nf-core create run: nf-core --log-file log.txt create -n testpipeline -d "This pipeline is for testing" -a "Testing McTestface" From 4d018b011411c29e45e6e60aaea5077eae6e714c Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Wed, 3 Nov 2021 11:46:04 +0000 Subject: [PATCH 111/266] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0ff592c45..87c3f19065 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Disable cache in `nf_core.utils.fetch_wf_config` while performing `test_wf_use_local_configs`. * Modify software version channel handling to support multiple software version emissions (e.g. from mulled containers), and multiple software versions. * Update `dumpsoftwareversion` module to correctly report versions with trailing zeros. +* Remove `params.hostnames` from the pipeline template ([#1304](https://github.com/nf-core/tools/issues/1304)) ### General @@ -15,6 +16,7 @@ * Update regex in `readme` test of `nf-core lint` to agree with the pipeline template ([#1260](https://github.com/nf-core/tools/issues/1260)) * Update 'fix' message in `nf-core lint` to conform to the current command line options. ([#1259](https://github.com/nf-core/tools/issues/1259)) * Fixed bug in `nf-core list` when `NXF_HOME` is set +* Run CI test used to create and lint/run the pipeline template with minimum and latest edge release of NF ([#1304](https://github.com/nf-core/tools/issues/1304)) ### Modules From b15c6c03e3947380a5b65b7808f3dc2bb7d609d0 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Wed, 3 Nov 2021 12:50:32 +0000 Subject: [PATCH 112/266] Add a check to see if any custom configuration is provided --- .../pipeline-template/lib/NfcoreTemplate.groovy | 14 ++++++++++++++ nf_core/pipeline-template/lib/WorkflowMain.groovy | 3 +++ 2 files changed, 17 insertions(+) diff --git a/nf_core/pipeline-template/lib/NfcoreTemplate.groovy b/nf_core/pipeline-template/lib/NfcoreTemplate.groovy index eb7c554070..46d99097d4 100755 --- a/nf_core/pipeline-template/lib/NfcoreTemplate.groovy +++ b/nf_core/pipeline-template/lib/NfcoreTemplate.groovy @@ -18,6 +18,20 @@ class NfcoreTemplate { } } + // + // Warn if a -profile or Nextflow config has not been provided to run the pipeline + // + 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 enviroment but can be acheived by:\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 awsbatch`\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 " + } + } + // // Construct and send completion email // diff --git a/nf_core/pipeline-template/lib/WorkflowMain.groovy b/nf_core/pipeline-template/lib/WorkflowMain.groovy index db05c0f9ad..3181f592ca 100755 --- a/nf_core/pipeline-template/lib/WorkflowMain.groovy +++ b/nf_core/pipeline-template/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) From 89af1d984b3eeefe3d8a985bcfe3abf4ee0d05e8 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Wed, 3 Nov 2021 12:57:11 +0000 Subject: [PATCH 113/266] Re-word warning --- nf_core/pipeline-template/lib/NfcoreTemplate.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nf_core/pipeline-template/lib/NfcoreTemplate.groovy b/nf_core/pipeline-template/lib/NfcoreTemplate.groovy index 46d99097d4..6d5a1d1078 100755 --- a/nf_core/pipeline-template/lib/NfcoreTemplate.groovy +++ b/nf_core/pipeline-template/lib/NfcoreTemplate.groovy @@ -24,11 +24,11 @@ class NfcoreTemplate { 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 enviroment but can be acheived by:\n" + + "This will be dependent on your local compute enviroment but can be acheived 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 awsbatch`\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 " + "Please refer to the quick start section and usage docs for the pipeline.\n " } } From 49b5642fdabbbceb50d7f47a067a8ca75045448e Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Wed, 3 Nov 2021 14:56:56 +0000 Subject: [PATCH 114/266] Remove remnant of params.hostnames --- nf_core/pipeline-template/lib/NfcoreTemplate.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/nf_core/pipeline-template/lib/NfcoreTemplate.groovy b/nf_core/pipeline-template/lib/NfcoreTemplate.groovy index 6d5a1d1078..06499409c5 100755 --- a/nf_core/pipeline-template/lib/NfcoreTemplate.groovy +++ b/nf_core/pipeline-template/lib/NfcoreTemplate.groovy @@ -157,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}-" } } From 74a82945d5928a3d6a97ab7a3cd3e4100a941ce6 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Mon, 8 Nov 2021 12:32:02 +0100 Subject: [PATCH 115/266] Auto-collapse installed modules and subworkflows in PRs See also https://nfcore.slack.com/archives/CE5LG7WMB/p1636276933143100 --- nf_core/pipeline-template/.gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nf_core/pipeline-template/.gitattributes b/nf_core/pipeline-template/.gitattributes index 7fe55006f8..050bb12035 100644 --- a/nf_core/pipeline-template/.gitattributes +++ b/nf_core/pipeline-template/.gitattributes @@ -1 +1,3 @@ *.config linguist-language=nextflow +modules/nf-core/** linguist-generated +subworkflows/nf-core/** linguist-generated From 26b12e3addfcf489a17a7556b812517bb2a5cf6d Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Mon, 8 Nov 2021 11:34:36 +0000 Subject: [PATCH 116/266] Update Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87c3f19065..727077c569 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Modify software version channel handling to support multiple software version emissions (e.g. from mulled containers), and multiple software versions. * Update `dumpsoftwareversion` module to correctly report versions with trailing zeros. * Remove `params.hostnames` from the pipeline template ([#1304](https://github.com/nf-core/tools/issues/1304)) +* Update `.gitattributes` to mark installed modules and subworkflows as `linguist-generated` ([#1311](https://github.com/nf-core/tools/issues/1311)) ### General From 880345107e6edf72ca11e9052fef24af335a54aa Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Tue, 9 Nov 2021 15:49:23 +0100 Subject: [PATCH 117/266] start using context manager for temporary files and folders --- tests/__init__.py | 7 ++++ tests/test_bump_version.py | 16 +++++---- tests/test_create.py | 8 +++-- tests/test_download.py | 71 +++++++++++++++++--------------------- tests/utils.py | 36 +++++++++++++++++++ 5 files changed, 89 insertions(+), 49 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/utils.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000000..b3e4769de7 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Tue Nov 9 13:46:10 2021 + +@author: Paolo Cozzi +""" diff --git a/tests/test_bump_version.py b/tests/test_bump_version.py index bd6c5a1a57..13a731eaa6 100644 --- a/tests/test_bump_version.py +++ b/tests/test_bump_version.py @@ -2,7 +2,6 @@ """Some tests covering the bump_version code. """ import os -import tempfile import yaml import nf_core.bump_version @@ -10,10 +9,13 @@ import nf_core.utils -def test_bump_pipeline_version(datafiles): +# pass tmp_path as argument, which is a pytest feature +# see: https://docs.pytest.org/en/latest/how-to/tmp_path.html#the-tmp-path-fixture +def test_bump_pipeline_version(datafiles, tmp_path): """Test that making a release with the working example files works""" + # Get a workflow and configs - test_pipeline_dir = os.path.join(tempfile.mkdtemp(), "nf-core-testpipeline") + test_pipeline_dir = os.path.join(tmp_path, "nf-core-testpipeline") create_obj = nf_core.create.PipelineCreate( "testpipeline", "This is a test pipeline", "Test McTestFace", outdir=test_pipeline_dir ) @@ -30,10 +32,10 @@ def test_bump_pipeline_version(datafiles): assert new_pipeline_obj.nf_config["manifest.version"].strip("'\"") == "1.1" -def test_dev_bump_pipeline_version(datafiles): +def test_dev_bump_pipeline_version(datafiles, tmp_path): """Test that making a release works with a dev name and a leading v""" # Get a workflow and configs - test_pipeline_dir = os.path.join(tempfile.mkdtemp(), "nf-core-testpipeline") + test_pipeline_dir = os.path.join(tmp_path, "nf-core-testpipeline") create_obj = nf_core.create.PipelineCreate( "testpipeline", "This is a test pipeline", "Test McTestFace", outdir=test_pipeline_dir ) @@ -50,9 +52,9 @@ def test_dev_bump_pipeline_version(datafiles): assert new_pipeline_obj.nf_config["manifest.version"].strip("'\"") == "1.2dev" -def test_bump_nextflow_version(datafiles): +def test_bump_nextflow_version(datafiles, tmp_path): # Get a workflow and configs - test_pipeline_dir = os.path.join(tempfile.mkdtemp(), "nf-core-testpipeline") + test_pipeline_dir = os.path.join(tmp_path, "nf-core-testpipeline") create_obj = nf_core.create.PipelineCreate( "testpipeline", "This is a test pipeline", "Test McTestFace", outdir=test_pipeline_dir ) diff --git a/tests/test_create.py b/tests/test_create.py index 5fb1e53a60..cce494b523 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -3,12 +3,14 @@ """ import os import nf_core.create -import tempfile import unittest +from .utils import with_temporary_folder + class NfcoreCreateTest(unittest.TestCase): - def setUp(self): + @with_temporary_folder + def setUp(self, tmp_path): self.pipeline_name = "nf-core/test" self.pipeline_description = "just for 4w3s0m3 tests" self.pipeline_author = "Chuck Norris" @@ -21,7 +23,7 @@ def setUp(self): version=self.pipeline_version, no_git=False, force=True, - outdir=tempfile.mkdtemp(), + outdir=tmp_path, ) def test_pipeline_creation(self): diff --git a/tests/test_download.py b/tests/test_download.py index 7025cd5ad5..f67a0b4f98 100644 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -14,6 +14,8 @@ import tempfile import unittest +from .utils import with_temporary_folder, with_temporary_file + class DownloadTest(unittest.TestCase): @@ -73,8 +75,8 @@ def test_get_release_hash_non_existent_release(self): # # Tests for 'download_wf_files' # - def test_download_wf_files(self): - outdir = tempfile.mkdtemp() + @with_temporary_folder + def test_download_wf_files(self, outdir): download_obj = DownloadWorkflow(pipeline="nf-core/methylseq", revision="1.6") download_obj.outdir = outdir download_obj.wf_sha = "b3e5e3b95aaf01d98391a62a10a3990c0a4de395" @@ -87,8 +89,8 @@ def test_download_wf_files(self): # # Tests for 'download_configs' # - def test_download_configs(self): - outdir = tempfile.mkdtemp() + @with_temporary_folder + def test_download_configs(self, outdir): download_obj = DownloadWorkflow(pipeline="nf-core/methylseq", revision="1.6") download_obj.outdir = outdir download_obj.download_configs() @@ -97,30 +99,32 @@ def test_download_configs(self): # # Tests for 'wf_use_local_configs' # - def test_wf_use_local_configs(self): + @with_temporary_folder + def test_wf_use_local_configs(self, tmp_path): # Get a workflow and configs - test_pipeline_dir = os.path.join(tempfile.mkdtemp(), "nf-core-testpipeline") + test_pipeline_dir = os.path.join(tmp_path, "nf-core-testpipeline") create_obj = nf_core.create.PipelineCreate( "testpipeline", "This is a test pipeline", "Test McTestFace", outdir=test_pipeline_dir ) create_obj.init_pipeline() - test_outdir = tempfile.mkdtemp() - download_obj = DownloadWorkflow(pipeline="dummy", revision="1.2.0", outdir=test_outdir) - shutil.copytree(test_pipeline_dir, os.path.join(test_outdir, "workflow")) - download_obj.download_configs() + with tempfile.TemporaryDirectory() as test_outdir: + download_obj = DownloadWorkflow(pipeline="dummy", revision="1.2.0", outdir=test_outdir) + shutil.copytree(test_pipeline_dir, os.path.join(test_outdir, "workflow")) + download_obj.download_configs() - # Test the function - download_obj.wf_use_local_configs() - wf_config = nf_core.utils.fetch_wf_config(os.path.join(test_outdir, "workflow"), cache_config=False) - assert wf_config["params.custom_config_base"] == f"'{test_outdir}/workflow/../configs/'" + # Test the function + download_obj.wf_use_local_configs() + wf_config = nf_core.utils.fetch_wf_config(os.path.join(test_outdir, "workflow"), cache_config=False) + assert wf_config["params.custom_config_base"] == f"'{test_outdir}/workflow/../configs/'" # # Tests for 'find_container_images' # + @with_temporary_folder @mock.patch("nf_core.utils.fetch_wf_config") - def test_find_container_images(self, mock_fetch_wf_config): - download_obj = DownloadWorkflow(pipeline="dummy", outdir=tempfile.mkdtemp()) + def test_find_container_images(self, tmp_path, mock_fetch_wf_config): + download_obj = DownloadWorkflow(pipeline="dummy", outdir=tmp_path) mock_fetch_wf_config.return_value = { "process.mapping.container": "cutting-edge-container", "process.nocontainer": "not-so-cutting-edge", @@ -132,60 +136,52 @@ def test_find_container_images(self, mock_fetch_wf_config): # # Tests for 'validate_md5' # - def test_matching_md5sums(self): + @with_temporary_file + def test_matching_md5sums(self, tmpfile): download_obj = DownloadWorkflow(pipeline="dummy") test_hash = hashlib.md5() test_hash.update(b"test") val_hash = test_hash.hexdigest() - tmpfilehandle, tmpfile = tempfile.mkstemp() - with open(tmpfile[1], "w") as f: + with open(tmpfile.name, "w") as f: f.write("test") - download_obj.validate_md5(tmpfile[1], val_hash) - - # Clean up - os.remove(tmpfile[1]) + download_obj.validate_md5(tmpfile.name, val_hash) + @with_temporary_file @pytest.mark.xfail(raises=IOError, strict=True) - def test_mismatching_md5sums(self): + def test_mismatching_md5sums(self, tmpfile): download_obj = DownloadWorkflow(pipeline="dummy") test_hash = hashlib.md5() test_hash.update(b"other value") val_hash = test_hash.hexdigest() - tmpfilehandle, tmpfile = tempfile.mkstemp() - with open(tmpfile, "w") as f: + with open(tmpfile.name, "w") as f: f.write("test") - download_obj.validate_md5(tmpfile[1], val_hash) + download_obj.validate_md5(tmpfile.name, val_hash) - # Clean up - os.remove(tmpfile) # # Tests for 'singularity_pull_image' # # If Singularity is not installed, will log an error and exit # If Singularity is installed, should raise an OSError due to non-existant image + @with_temporary_folder @pytest.mark.xfail(raises=OSError) @mock.patch("rich.progress.Progress.add_task") - def test_singularity_pull_image(self, mock_rich_progress): - tmp_dir = tempfile.mkdtemp() + def test_singularity_pull_image(self, tmp_dir, mock_rich_progress): download_obj = DownloadWorkflow(pipeline="dummy", outdir=tmp_dir) download_obj.singularity_pull_image("a-container", tmp_dir, None, mock_rich_progress) - # Clean up - shutil.rmtree(tmp_dir) # # Tests for the main entry method 'download_workflow' # + @with_temporary_folder @mock.patch("nf_core.download.DownloadWorkflow.singularity_pull_image") @mock.patch("shutil.which") - def test_download_workflow_with_success(self, mock_download_image, mock_singularity_installed): - - tmp_dir = tempfile.mkdtemp() + def test_download_workflow_with_success(self, tmp_dir, mock_download_image, mock_singularity_installed): os.environ["NXF_SINGULARITY_CACHEDIR"] = "foo" download_obj = DownloadWorkflow( @@ -197,6 +193,3 @@ def test_download_workflow_with_success(self, mock_download_image, mock_singular ) download_obj.download_workflow() - - # Clean up - shutil.rmtree(tmp_dir) diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000000..8acd72ce7b --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Helper functions for tests +""" + +import functools +import tempfile + + +def with_temporary_folder(func): + """ + Call the decorated funtion under the tempfile.TemporaryDirectory + context manager. Pass the temporary directory name to the decorated + function + """ + + @functools.wraps(func) + def wrapper(*args, **kwargs): + with tempfile.TemporaryDirectory() as tmpdirname: + return func(*args, tmpdirname, **kwargs) + + return wrapper + + +def with_temporary_file(func): + """ + Call the decorated funtion under the tempfile.NamedTemporaryFile + context manager. Pass the opened file handle to the decorated function + """ + @functools.wraps(func) + def wrapper(*args, **kwargs): + with tempfile.NamedTemporaryFile() as tmpfile: + return func(*args, tmpfile, **kwargs) + + return wrapper From cc9845f8f3277c1051dd546808bf5d6f0bf92338 Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Tue, 9 Nov 2021 17:25:14 +0100 Subject: [PATCH 118/266] refactor test_launch deal with temporary files in test_launch --- tests/test_launch.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/tests/test_launch.py b/tests/test_launch.py index 090414029c..8029213f06 100644 --- a/tests/test_launch.py +++ b/tests/test_launch.py @@ -11,6 +11,8 @@ import tempfile import unittest +from .utils import with_temporary_folder, with_temporary_file + class TestLaunch(unittest.TestCase): """Class for launch tests""" @@ -20,9 +22,21 @@ def setUp(self): # Set up the schema root_repo_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) self.template_dir = os.path.join(root_repo_dir, "nf_core", "pipeline-template") - self.nf_params_fn = os.path.join(tempfile.mkdtemp(), "nf-params.json") + # cannot use a context manager here, since outside setUp the temporary + # file will never exists + self.tmp_dir = tempfile.mkdtemp() + self.nf_params_fn = os.path.join(self.tmp_dir, "nf-params.json") self.launcher = nf_core.launch.Launch(self.template_dir, params_out=self.nf_params_fn) + def tearDown(self): + """Clean up temporary files and folders""" + + if os.path.exists(self.nf_params_fn): + os.remove(self.nf_params_fn) + + if os.path.exists(self.tmp_dir): + os.rmdir(self.tmp_dir) + @mock.patch.object(nf_core.launch.Launch, "prompt_web_gui", side_effect=[True]) @mock.patch.object(nf_core.launch.Launch, "launch_web_gui") def test_launch_pipeline(self, mock_webbrowser, mock_lauch_web_gui): @@ -52,9 +66,10 @@ def test_get_pipeline_schema(self): self.launcher.get_pipeline_schema() assert len(self.launcher.schema_obj.schema["definitions"]["input_output_options"]["properties"]) > 2 - def test_make_pipeline_schema(self): + @with_temporary_folder + def test_make_pipeline_schema(self, tmp_path): """Make a copy of the template workflow, but delete the schema file, then try to load it""" - test_pipeline_dir = os.path.join(tempfile.mkdtemp(), "wf") + test_pipeline_dir = os.path.join(tmp_path, "wf") shutil.copytree(self.template_dir, test_pipeline_dir) os.remove(os.path.join(test_pipeline_dir, "nextflow_schema.json")) self.launcher = nf_core.launch.Launch(test_pipeline_dir, params_out=self.nf_params_fn) @@ -74,12 +89,12 @@ def test_get_pipeline_defaults(self): assert len(self.launcher.schema_obj.input_params) > 0 assert self.launcher.schema_obj.input_params["outdir"] == "./results" - def test_get_pipeline_defaults_input_params(self): + @with_temporary_file + def test_get_pipeline_defaults_input_params(self, tmp_file): """Test fetching default inputs from the pipeline schema with an input params file supplied""" - tmp_filehandle, tmp_filename = tempfile.mkstemp() - with os.fdopen(tmp_filehandle, "w") as fh: + with open(tmp_file.name, "w") as fh: json.dump({"outdir": "fubar"}, fh) - self.launcher.params_in = tmp_filename + self.launcher.params_in = tmp_file.name self.launcher.get_pipeline_schema() self.launcher.set_schema_inputs() assert len(self.launcher.schema_obj.input_params) > 0 From 2d38e0af21537c46b1596944670cdec0499b160d Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Tue, 9 Nov 2021 17:26:25 +0100 Subject: [PATCH 119/266] deal with temporary files in test_lint --- nf_core/lint/files_unchanged.py | 7 +++++- tests/test_lint.py | 43 +++++++++++++++++++++------------ 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/nf_core/lint/files_unchanged.py b/nf_core/lint/files_unchanged.py index 262e8c4449..afe20eedcb 100644 --- a/nf_core/lint/files_unchanged.py +++ b/nf_core/lint/files_unchanged.py @@ -107,7 +107,9 @@ def files_unchanged(self): logging.getLogger("nf_core.create").setLevel(logging.ERROR) # Generate a new pipeline with nf-core create that we can compare to - test_pipeline_dir = os.path.join(tempfile.mkdtemp(), "nf-core-{}".format(short_name)) + tmp_dir = tempfile.mkdtemp() + + test_pipeline_dir = os.path.join(tmp_dir, "nf-core-{}".format(short_name)) create_obj = nf_core.create.PipelineCreate( self.nf_config["manifest.name"].strip("\"'"), self.nf_config["manifest.description"].strip("\"'"), @@ -192,4 +194,7 @@ def _tf(file_path): except FileNotFoundError: pass + # cleaning up temporary dir + shutil.rmtree(tmp_dir) + return {"passed": passed, "failed": failed, "ignored": ignored, "fixed": fixed, "could_fix": could_fix} diff --git a/tests/test_lint.py b/tests/test_lint.py index c6d5dc351d..78c8c26aee 100644 --- a/tests/test_lint.py +++ b/tests/test_lint.py @@ -16,6 +16,8 @@ import nf_core.create import nf_core.lint +from .utils import with_temporary_folder + class TestLint(unittest.TestCase): """Class for lint tests""" @@ -25,7 +27,9 @@ def setUp(self): Use nf_core.create() to make a pipeline that we can use for testing """ - self.test_pipeline_dir = os.path.join(tempfile.mkdtemp(), "nf-core-testpipeline") + + self.tmp_dir = tempfile.mkdtemp() + self.test_pipeline_dir = os.path.join(self.tmp_dir, "nf-core-testpipeline") self.create_obj = nf_core.create.PipelineCreate( "testpipeline", "This is a test pipeline", "Test McTestFace", outdir=self.test_pipeline_dir ) @@ -33,11 +37,17 @@ def setUp(self): # Base lint object on this directory self.lint_obj = nf_core.lint.PipelineLint(self.test_pipeline_dir) + def tearDown(self): + """Clean up temporary files and folders""" + + if os.path.exists(self.tmp_dir): + shutil.rmtree(self.tmp_dir) + def _make_pipeline_copy(self): """Make a copy of the test pipeline that can be edited Returns: Path to new temp directory with pipeline""" - new_pipeline = os.path.join(tempfile.mkdtemp(), "nf-core-testpipeline") + new_pipeline = os.path.join(self.tmp_dir, "nf-core-testpipeline-copy") shutil.copytree(self.test_pipeline_dir, new_pipeline) return new_pipeline @@ -72,9 +82,9 @@ def test_load_lint_config_not_found(self): def test_load_lint_config_ignore_all_tests(self): """Try to load a linting config file that ignores all tests""" + # Make a copy of the test pipeline and create a lint object - new_pipeline = os.path.join(tempfile.mkdtemp(), "nf-core-testpipeline") - shutil.copytree(self.test_pipeline_dir, new_pipeline) + new_pipeline = self._make_pipeline_copy() lint_obj = nf_core.lint.PipelineLint(new_pipeline) # Make a config file listing all test names @@ -93,7 +103,8 @@ def test_load_lint_config_ignore_all_tests(self): assert len(lint_obj.failed) == 0 assert len(lint_obj.ignored) == len(lint_obj.lint_tests) - def test_json_output(self): + @with_temporary_folder + def test_json_output(self, tmp_dir): """ Test creation of a JSON file with lint results @@ -122,7 +133,7 @@ def test_json_output(self): self.lint_obj.warned.append(("test_three", "This test gave a warning")) # Make a temp dir for the JSON output - json_fn = os.path.join(tempfile.mkdtemp(), "lint_results.json") + json_fn = os.path.join(tmp_dir, "lint_results.json") self.lint_obj._save_json_results(json_fn) # Load created JSON file and check its contents @@ -176,46 +187,46 @@ def test_sphinx_rst_files(self): ####################### # SPECIFIC LINT TESTS # ####################### - from lint.actions_awsfulltest import ( + from .lint.actions_awsfulltest import ( test_actions_awsfulltest_warn, test_actions_awsfulltest_pass, test_actions_awsfulltest_fail, ) - from lint.actions_awstest import test_actions_awstest_pass, test_actions_awstest_fail - from lint.files_exist import ( + from .lint.actions_awstest import test_actions_awstest_pass, test_actions_awstest_fail + from .lint.files_exist import ( test_files_exist_missing_config, test_files_exist_missing_main, test_files_exist_depreciated_file, test_files_exist_pass, ) - from lint.actions_ci import ( + from .lint.actions_ci import ( test_actions_ci_pass, test_actions_ci_fail_wrong_nf, test_actions_ci_fail_wrong_docker_ver, test_actions_ci_fail_wrong_trigger, ) - from lint.actions_schema_validation import ( + from .lint.actions_schema_validation import ( test_actions_schema_validation_missing_jobs, test_actions_schema_validation_missing_on, ) - from lint.merge_markers import test_merge_markers_found + from .lint.merge_markers import test_merge_markers_found - from lint.nextflow_config import ( + from .lint.nextflow_config import ( test_nextflow_config_example_pass, test_nextflow_config_bad_name_fail, test_nextflow_config_dev_in_release_mode_failed, ) - from lint.files_unchanged import ( + from .lint.files_unchanged import ( test_files_unchanged_pass, test_files_unchanged_fail, ) - from lint.version_consistency import test_version_consistency + from .lint.version_consistency import test_version_consistency - from lint.modules_json import test_modules_json_pass + from .lint.modules_json import test_modules_json_pass # TODO nf-core: Assess and strip out if no longer required for DSL2 From 74eaab96ef1236b65e979e6b16877741e223bf59 Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Tue, 9 Nov 2021 17:59:34 +0100 Subject: [PATCH 120/266] solve circular import issue in nf_core.lint --- CHANGELOG.md | 1 + nf_core/lint/__init__.py | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 727077c569..a3eb4c0fda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Template +* Solve circular import when importing `nf_core.modules.lint` * Disable cache in `nf_core.utils.fetch_wf_config` while performing `test_wf_use_local_configs`. * Modify software version channel handling to support multiple software version emissions (e.g. from mulled containers), and multiple software versions. * Update `dumpsoftwareversion` module to correctly report versions with trailing zeros. diff --git a/nf_core/lint/__init__.py b/nf_core/lint/__init__.py index b61765b162..4838726928 100644 --- a/nf_core/lint/__init__.py +++ b/nf_core/lint/__init__.py @@ -20,8 +20,8 @@ import nf_core.utils import nf_core.lint_utils +import nf_core.modules.lint from nf_core.lint_utils import console -from nf_core.modules.lint import ModuleLint log = logging.getLogger(__name__) @@ -44,7 +44,9 @@ def run_linting( # Verify that the requested tests exist if key: - all_tests = set(PipelineLint._get_all_lint_tests(release_mode)).union(set(ModuleLint._get_all_lint_tests())) + all_tests = set(PipelineLint._get_all_lint_tests(release_mode)).union( + set(nf_core.modules.lint.ModuleLint._get_all_lint_tests()) + ) bad_keys = [k for k in key if k not in all_tests] if len(bad_keys) > 0: raise AssertionError( @@ -66,7 +68,7 @@ def run_linting( lint_obj._list_files() # Create the modules lint object - module_lint_obj = ModuleLint(pipeline_dir) + module_lint_obj = nf_core.modules.lint.ModuleLint(pipeline_dir) # Verify that the pipeline is correctly configured try: @@ -77,7 +79,7 @@ def run_linting( # Run only the tests we want if key: # Select only the module lint tests - module_lint_tests = list(set(key).intersection(set(ModuleLint._get_all_lint_tests()))) + module_lint_tests = list(set(key).intersection(set(nf_core.modules.lint.ModuleLint._get_all_lint_tests()))) else: # If no key is supplied, run the default modules tests module_lint_tests = ("module_changes", "module_version") From 3556d74d08b1402ebe8a636b5f0b1973e3652dd7 Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Tue, 9 Nov 2021 19:02:41 +0100 Subject: [PATCH 121/266] deal with temporary files in test_modules --- tests/modules/create_test_yml.py | 15 ++++++++------- tests/modules/install.py | 8 +++++--- tests/test_modules.py | 30 +++++++++++++++++++----------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/tests/modules/create_test_yml.py b/tests/modules/create_test_yml.py index db286181c5..4e7bf1ab32 100644 --- a/tests/modules/create_test_yml.py +++ b/tests/modules/create_test_yml.py @@ -1,13 +1,14 @@ import os -import tempfile import pytest import nf_core.modules +from ..utils import with_temporary_folder -def test_modules_custom_yml_dumper(self): + +@with_temporary_folder +def test_modules_custom_yml_dumper(self, out_dir): """Try to create a yml file with the custom yml dumper""" - out_dir = tempfile.mkdtemp() yml_output_path = os.path.join(out_dir, "test.yml") meta_builder = nf_core.modules.ModulesTestYmlBuilder("test/tool", False, "./", False, True) meta_builder.test_yml_output_path = yml_output_path @@ -16,9 +17,9 @@ def test_modules_custom_yml_dumper(self): assert os.path.isfile(yml_output_path) -def test_modules_test_file_dict(self): +@with_temporary_folder +def test_modules_test_file_dict(self, test_file_dir): """Creat dict of test files and create md5 sums""" - test_file_dir = tempfile.mkdtemp() meta_builder = nf_core.modules.ModulesTestYmlBuilder("test/tool", False, "./", False, True) with open(os.path.join(test_file_dir, "test_file.txt"), "w") as fh: fh.write("this line is just for testing") @@ -27,9 +28,9 @@ def test_modules_test_file_dict(self): assert test_files[0]["md5sum"] == "2191e06b28b5ba82378bcc0672d01786" -def test_modules_create_test_yml_get_md5(self): +@with_temporary_folder +def test_modules_create_test_yml_get_md5(self, test_file_dir): """Get md5 sums from a dummy output""" - test_file_dir = tempfile.mkdtemp() meta_builder = nf_core.modules.ModulesTestYmlBuilder("test/tool", False, "./", False, True) with open(os.path.join(test_file_dir, "test_file.txt"), "w") as fh: fh.write("this line is just for testing") diff --git a/tests/modules/install.py b/tests/modules/install.py index 41307e3666..e4c94f6bdb 100644 --- a/tests/modules/install.py +++ b/tests/modules/install.py @@ -1,7 +1,8 @@ -import tempfile import pytest import os +from ..utils import with_temporary_folder + def test_modules_install_nopipeline(self): """Test installing a module - no pipeline given""" @@ -9,9 +10,10 @@ def test_modules_install_nopipeline(self): assert self.mods_install.install("foo") is False -def test_modules_install_emptypipeline(self): +@with_temporary_folder +def test_modules_install_emptypipeline(self, tmpdir): """Test installing a module - empty dir given""" - self.mods_install.dir = tempfile.mkdtemp() + self.mods_install.dir = tmpdir with pytest.raises(UserWarning) as excinfo: self.mods_install.install("foo") assert "Could not find a 'main.nf' or 'nextflow.config' file" in str(excinfo.value) diff --git a/tests/test_modules.py b/tests/test_modules.py index 2401dfa763..8b46cfc0fc 100644 --- a/tests/test_modules.py +++ b/tests/test_modules.py @@ -10,9 +10,9 @@ import unittest -def create_modules_repo_dummy(): +def create_modules_repo_dummy(tmp_dir): """Create a dummy copy of the nf-core/modules repo""" - tmp_dir = tempfile.mkdtemp() + root_dir = os.path.join(tmp_dir, "modules") os.makedirs(os.path.join(root_dir, "modules")) os.makedirs(os.path.join(root_dir, "tests", "modules")) @@ -31,10 +31,12 @@ class TestModules(unittest.TestCase): def setUp(self): """Create a new PipelineSchema and Launch objects""" + self.tmp_dir = tempfile.mkdtemp() + # Set up the schema root_repo_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) self.template_dir = os.path.join(root_repo_dir, "nf_core", "pipeline-template") - self.pipeline_dir = os.path.join(tempfile.mkdtemp(), "mypipeline") + self.pipeline_dir = os.path.join(self.tmp_dir, "mypipeline") shutil.copytree(self.template_dir, self.pipeline_dir) # Set up install objects @@ -53,7 +55,13 @@ def setUp(self): # self.mods_remove_alt.modules_repo = nf_core.modules.ModulesRepo(repo="ewels/nf-core-modules", branch="master") # Set up the nf-core/modules repo dummy - self.nfcore_modules = create_modules_repo_dummy() + self.nfcore_modules = create_modules_repo_dummy(self.tmp_dir) + + def tearDown(self): + """Clean up temporary files and folders""" + + if os.path.exists(self.tmp_dir): + shutil.rmtree(self.tmp_dir) def test_modulesrepo_class(self): """Initialise a modules repo object""" @@ -65,13 +73,13 @@ def test_modulesrepo_class(self): # Test of the individual modules commands. # ############################################ - from modules.list import ( + from .modules.list import ( test_modules_list_remote, test_modules_list_pipeline, test_modules_install_and_list_pipeline, ) - from modules.install import ( + from .modules.install import ( test_modules_install_nopipeline, test_modules_install_emptypipeline, test_modules_install_nomodule, @@ -79,21 +87,21 @@ def test_modulesrepo_class(self): test_modules_install_trimgalore_twice, ) - from modules.remove import ( + from .modules.remove import ( test_modules_remove_trimgalore, test_modules_remove_trimgalore_uninstalled, ) - from modules.lint import test_modules_lint_trimgalore, test_modules_lint_empty, test_modules_lint_new_modules + from .modules.lint import test_modules_lint_trimgalore, test_modules_lint_empty, test_modules_lint_new_modules - from modules.create import ( + from .modules.create import ( test_modules_create_succeed, test_modules_create_fail_exists, test_modules_create_nfcore_modules, test_modules_create_nfcore_modules_subtool, ) - from modules.create_test_yml import ( + from .modules.create_test_yml import ( test_modules_custom_yml_dumper, test_modules_test_file_dict, test_modules_create_test_yml_get_md5, @@ -101,7 +109,7 @@ def test_modulesrepo_class(self): test_modules_create_test_yml_check_inputs, ) - from modules.bump_versions import ( + from .modules.bump_versions import ( test_modules_bump_versions_single_module, test_modules_bump_versions_all_modules, test_modules_bump_versions_fail, From c1a9c3709e6d7e10cdec5465580c1e2e9389f0ca Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Tue, 9 Nov 2021 19:25:34 +0100 Subject: [PATCH 122/266] deal with temporary files in schema, sync and utils tests --- tests/test_schema.py | 36 ++++++++++++++++++++++-------------- tests/test_sync.py | 18 ++++++++++++------ tests/test_utils.py | 16 ++++++++++++---- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 175b23880c..3a060a516c 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -15,6 +15,8 @@ import unittest import yaml +from .utils import with_temporary_file, with_temporary_folder + class TestSchema(unittest.TestCase): """Class for schema tests""" @@ -24,11 +26,16 @@ def setUp(self): self.schema_obj = nf_core.schema.PipelineSchema() self.root_repo_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) # Copy the template to a temp directory so that we can use that for tests - self.template_dir = os.path.join(tempfile.mkdtemp(), "wf") + self.tmp_dir = tempfile.mkdtemp() + self.template_dir = os.path.join(self.tmp_dir, "wf") template_dir = os.path.join(self.root_repo_dir, "nf_core", "pipeline-template") shutil.copytree(template_dir, self.template_dir) self.template_schema = os.path.join(self.template_dir, "nextflow_schema.json") + def tearDown(self): + if os.path.exists(self.tmp_dir): + shutil.rmtree(self.tmp_dir) + def test_load_lint_schema(self): """Check linting with the pipeline template directory""" self.schema_obj.get_schema_path(self.template_dir) @@ -46,13 +53,13 @@ def test_load_lint_schema_notjson(self): self.schema_obj.get_schema_path(os.path.join(self.template_dir, "nextflow.config")) self.schema_obj.load_lint_schema() + @with_temporary_file @pytest.mark.xfail(raises=AssertionError, strict=True) - def test_load_lint_schema_noparams(self): + def test_load_lint_schema_noparams(self, tmp_file): """ Check that linting raises properly if a JSON file is given without any params """ - # Make a temporary file to write schema to - tmp_file = tempfile.NamedTemporaryFile() + # write schema to a temporary file with open(tmp_file.name, "w") as fh: json.dump({"type": "fubar"}, fh) self.schema_obj.get_schema_path(tmp_file.name) @@ -88,29 +95,29 @@ def test_load_schema(self): self.schema_obj.schema_filename = self.template_schema self.schema_obj.load_schema() - def test_save_schema(self): + @with_temporary_file + def test_save_schema(self, tmp_file): """Try to save a schema""" # Load the template schema self.schema_obj.schema_filename = self.template_schema self.schema_obj.load_schema() # Make a temporary file to write schema to - tmp_file = tempfile.NamedTemporaryFile() self.schema_obj.schema_filename = tmp_file.name self.schema_obj.save_schema() - def test_load_input_params_json(self): + @with_temporary_file + def test_load_input_params_json(self, tmp_file): """Try to load a JSON file with params for a pipeline run""" - # Make a temporary file to write schema to - tmp_file = tempfile.NamedTemporaryFile() + # write schema to a temporary file with open(tmp_file.name, "w") as fh: json.dump({"input": "fubar"}, fh) self.schema_obj.load_input_params(tmp_file.name) - def test_load_input_params_yaml(self): + @with_temporary_file + def test_load_input_params_yaml(self, tmp_file): """Try to load a YAML file with params for a pipeline run""" - # Make a temporary file to write schema to - tmp_file = tempfile.NamedTemporaryFile() + # write schema to a temporary file with open(tmp_file.name, "w") as fh: yaml.dump({"input": "fubar"}, fh) self.schema_obj.load_input_params(tmp_file.name) @@ -293,14 +300,15 @@ def test_build_schema(self): """ param = self.schema_obj.build_schema(self.template_dir, True, False, None) - def test_build_schema_from_scratch(self): + @with_temporary_folder + def test_build_schema_from_scratch(self, tmp_dir): """ Build a new schema param from a pipeline with no existing file Run code to ensure it doesn't crash. Individual functions tested separately. Pretty much a copy of test_launch.py test_make_pipeline_schema """ - test_pipeline_dir = os.path.join(tempfile.mkdtemp(), "wf") + test_pipeline_dir = os.path.join(tmp_dir, "wf") shutil.copytree(self.template_dir, test_pipeline_dir) os.remove(os.path.join(test_pipeline_dir, "nextflow_schema.json")) diff --git a/tests/test_sync.py b/tests/test_sync.py index ce7d07dc7f..727db70104 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -5,6 +5,7 @@ import nf_core.create import nf_core.sync +import git import json import mock import os @@ -12,22 +13,27 @@ import tempfile import unittest +from .utils import with_temporary_folder + class TestModules(unittest.TestCase): """Class for modules tests""" def setUp(self): - self.make_new_pipeline() - - def make_new_pipeline(self): """Create a new pipeline to test""" - self.pipeline_dir = os.path.join(tempfile.mkdtemp(), "test_pipeline") + self.tmp_dir = tempfile.mkdtemp() + self.pipeline_dir = os.path.join(self.tmp_dir, "test_pipeline") self.create_obj = nf_core.create.PipelineCreate("testing", "test pipeline", "tester", outdir=self.pipeline_dir) self.create_obj.init_pipeline() - def test_inspect_sync_dir_notgit(self): + def tearDown(self): + if os.path.exists(self.tmp_dir): + shutil.rmtree(self.tmp_dir) + + @with_temporary_folder + def test_inspect_sync_dir_notgit(self, tmp_dir): """Try syncing an empty directory""" - psync = nf_core.sync.PipelineSync(tempfile.mkdtemp()) + psync = nf_core.sync.PipelineSync(tmp_dir) try: psync.inspect_sync_dir() raise UserWarning("Should have hit an exception") diff --git a/tests/test_utils.py b/tests/test_utils.py index 36d533afe4..b62a8c979b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -12,6 +12,9 @@ import requests import tempfile import unittest +import shutil + +from .utils import with_temporary_folder class TestUtils(unittest.TestCase): @@ -22,7 +25,8 @@ def setUp(self): Use nf_core.create() to make a pipeline that we can use for testing """ - self.test_pipeline_dir = os.path.join(tempfile.mkdtemp(), "nf-core-testpipeline") + self.tmp_dir = tempfile.mkdtemp() + self.test_pipeline_dir = os.path.join(self.tmp_dir, "nf-core-testpipeline") self.create_obj = nf_core.create.PipelineCreate( "testpipeline", "This is a test pipeline", "Test McTestFace", outdir=self.test_pipeline_dir ) @@ -30,6 +34,10 @@ def setUp(self): # Base Pipeline object on this directory self.pipeline_obj = nf_core.utils.Pipeline(self.test_pipeline_dir) + def tearDown(self): + if os.path.exists(self.tmp_dir): + shutil.rmtree(self.tmp_dir) + def test_check_if_outdated_1(self): current_version = "1.0" remote_version = "2.0" @@ -89,10 +97,10 @@ def test_list_files_git(self): self.pipeline_obj._list_files() assert os.path.join(self.test_pipeline_dir, "main.nf") in self.pipeline_obj.files - def test_list_files_no_git(self): + @with_temporary_folder + def test_list_files_no_git(self, tmpdir): """Test listing pipeline files without `git-ls`""" - # Create directory with a test file - tmpdir = tempfile.mkdtemp() + # Create a test file in a temporary directory tmp_fn = os.path.join(tmpdir, "testfile") open(tmp_fn, "a").close() pipeline_obj = nf_core.utils.Pipeline(tmpdir) From 4067720c3746148343ba99f869e20f8658a4cec2 Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Tue, 9 Nov 2021 19:27:18 +0100 Subject: [PATCH 123/266] update changelog and run black --- CHANGELOG.md | 1 + tests/test_download.py | 2 -- tests/utils.py | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 727077c569..d913559b80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Template +* Erase temporary files and folders while performing tests * Disable cache in `nf_core.utils.fetch_wf_config` while performing `test_wf_use_local_configs`. * Modify software version channel handling to support multiple software version emissions (e.g. from mulled containers), and multiple software versions. * Update `dumpsoftwareversion` module to correctly report versions with trailing zeros. diff --git a/tests/test_download.py b/tests/test_download.py index f67a0b4f98..2dcc7b8cc5 100644 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -161,7 +161,6 @@ def test_mismatching_md5sums(self, tmpfile): download_obj.validate_md5(tmpfile.name, val_hash) - # # Tests for 'singularity_pull_image' # @@ -174,7 +173,6 @@ def test_singularity_pull_image(self, tmp_dir, mock_rich_progress): download_obj = DownloadWorkflow(pipeline="dummy", outdir=tmp_dir) download_obj.singularity_pull_image("a-container", tmp_dir, None, mock_rich_progress) - # # Tests for the main entry method 'download_workflow' # diff --git a/tests/utils.py b/tests/utils.py index 8acd72ce7b..1f40525707 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -28,6 +28,7 @@ def with_temporary_file(func): Call the decorated funtion under the tempfile.NamedTemporaryFile context manager. Pass the opened file handle to the decorated function """ + @functools.wraps(func) def wrapper(*args, **kwargs): with tempfile.NamedTemporaryFile() as tmpfile: From c00f2632966b33692f54e920e46579fbdafd4aab Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Wed, 10 Nov 2021 21:39:48 +0100 Subject: [PATCH 124/266] Add Julia support Adding in a line to enable support for Julia packages. --- nf_core/pipeline-template/nextflow.config | 1 + 1 file changed, 1 insertion(+) diff --git a/nf_core/pipeline-template/nextflow.config b/nf_core/pipeline-template/nextflow.config index 3f23f45b34..5d37a05223 100644 --- a/nf_core/pipeline-template/nextflow.config +++ b/nf_core/pipeline-template/nextflow.config @@ -131,6 +131,7 @@ 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 From f89c7a4e3e73a71bc6f5cd2e6ff26def59218c59 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Wed, 10 Nov 2021 21:56:23 +0100 Subject: [PATCH 125/266] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 727077c569..7de988738a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Update `dumpsoftwareversion` module to correctly report versions with trailing zeros. * Remove `params.hostnames` from the pipeline template ([#1304](https://github.com/nf-core/tools/issues/1304)) * Update `.gitattributes` to mark installed modules and subworkflows as `linguist-generated` ([#1311](https://github.com/nf-core/tools/issues/1311)) +* Adding support for [Julia](https://julialang.org) package environments to `nextflow.config`([#1317](https://github.com/nf-core/tools/pull/1317)) ### General From b2f0afc46f7e9ebf8aff418e99f2dd6bfa175b6d Mon Sep 17 00:00:00 2001 From: "Robert A. Petit III" Date: Tue, 16 Nov 2021 13:54:06 -0700 Subject: [PATCH 126/266] check for nextflow.config --- nf_core/modules/create.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/modules/create.py b/nf_core/modules/create.py index b8fc6d30db..1e0378b20c 100644 --- a/nf_core/modules/create.py +++ b/nf_core/modules/create.py @@ -282,7 +282,7 @@ def get_repo_type(self, directory): raise UserWarning(f"Could not find directory: {directory}") # Determine repository type - if os.path.exists(os.path.join(directory, "main.nf")): + if os.path.exists(os.path.join(directory, "nextflow.config")): return "pipeline" elif os.path.exists(os.path.join(directory, "modules")): return "modules" From 37984b601608675426eff6d6f2b778042e8d0ad0 Mon Sep 17 00:00:00 2001 From: "Robert A. Petit III" Date: Tue, 16 Nov 2021 14:09:06 -0700 Subject: [PATCH 127/266] check if readme is from modules --- nf_core/modules/create.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nf_core/modules/create.py b/nf_core/modules/create.py index 1e0378b20c..3b2b200abf 100644 --- a/nf_core/modules/create.py +++ b/nf_core/modules/create.py @@ -282,10 +282,12 @@ def get_repo_type(self, directory): raise UserWarning(f"Could not find directory: {directory}") # Determine repository type - if os.path.exists(os.path.join(directory, "nextflow.config")): - return "pipeline" - elif os.path.exists(os.path.join(directory, "modules")): - return "modules" + if os.path.exists("README.md"): + with open("README.md") as fh: + if fh.readline().rstrip().startswith("# ![nf-core/modules]"): + return "modules" + else: + return "pipeline" else: raise UserWarning( f"This directory does not look like a clone of nf-core/modules or an nf-core pipeline: '{directory}'" From 19b9a69e913f95982174edab82abc2a9e97d2cd6 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 16 Nov 2021 23:16:06 +0100 Subject: [PATCH 128/266] Apply suggestions from code review --- nf_core/lint/schema_params.py | 2 +- nf_core/schema.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/lint/schema_params.py b/nf_core/lint/schema_params.py index 6ec34f6adb..79688491f0 100644 --- a/nf_core/lint/schema_params.py +++ b/nf_core/lint/schema_params.py @@ -45,6 +45,6 @@ def schema_params(self): if len(invalid_config_default_params) > 0: for param in invalid_config_default_params: - warned.append(f"Default value for param `{param}` invalid or missing in nextflow config") + failed.append(f"Default value for param `{param}` invalid or missing in nextflow config") return {"passed": passed, "warned": warned, "failed": failed} diff --git a/nf_core/schema.py b/nf_core/schema.py index a76006f2b4..4e1235e485 100644 --- a/nf_core/schema.py +++ b/nf_core/schema.py @@ -226,7 +226,7 @@ def validate_default_params(self): log.error("[red][✗] Pipeline schema not found") except jsonschema.exceptions.ValidationError as e: raise AssertionError("Default parameters are invalid: {}".format(e.message)) - log.info("[green][✓] Default parameters look valid") + log.info("[green][✓] Default parameters match schema validation") # Make sure every default parameter exists in the nextflow.config and is of correct type if self.pipeline_params == {}: From 27b6453cdb71755440474772eb5ab180572b29fa Mon Sep 17 00:00:00 2001 From: "Robert A. Petit III" Date: Tue, 16 Nov 2021 15:25:20 -0700 Subject: [PATCH 129/266] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 727077c569..e5561b2117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ * Fixed typo in `module_utils.py`. * Added `--diff` flag to `nf-core modules update` which shows the diff between the installed files and the versions * Update `nf-core modules create` help texts which were not changed with the introduction of the `--dir` flag +* Check if README is from modules repo ## [v2.1 - Zinc Zebra](https://github.com/nf-core/tools/releases/tag/2.1) - [2021-07-27] From 60384ad9f765b584ab7860cf7e3ce61e924a32cb Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 16 Nov 2021 23:56:56 +0100 Subject: [PATCH 130/266] Improvements to stronger lint checks for param defaults. - Make new code work with `nf-core schema lint` too - Log type of problem with the default, as it can be several different things - Remove code that stripped schema param if config was set to false / empty string etc --- nf_core/lint/schema_params.py | 4 +-- nf_core/schema.py | 53 ++++++++++++++++++++++------------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/nf_core/lint/schema_params.py b/nf_core/lint/schema_params.py index 79688491f0..436e8caf54 100644 --- a/nf_core/lint/schema_params.py +++ b/nf_core/lint/schema_params.py @@ -44,7 +44,7 @@ def schema_params(self): passed.append("Schema matched params returned from nextflow config") if len(invalid_config_default_params) > 0: - for param in invalid_config_default_params: - failed.append(f"Default value for param `{param}` invalid or missing in nextflow config") + for param, msg in invalid_config_default_params.items(): + failed.append(f"Default value for param `{param}` invalid: {msg}") return {"passed": passed, "warned": warned, "failed": failed} diff --git a/nf_core/schema.py b/nf_core/schema.py index 4e1235e485..7128a03bc8 100644 --- a/nf_core/schema.py +++ b/nf_core/schema.py @@ -38,7 +38,7 @@ def __init__(self): self.schema_params = [] self.input_params = {} self.pipeline_params = {} - self.invalid_nextflow_config_default_parameters = [] + self.invalid_nextflow_config_default_parameters = {} self.pipeline_manifest = {} self.schema_from_scratch = False self.no_prompts = False @@ -83,7 +83,19 @@ def load_lint_schema(self): num_params = self.validate_schema() self.get_schema_defaults() self.validate_default_params() - log.info("[green][✓] Pipeline schema looks valid[/] [dim](found {} params)".format(num_params)) + if len(self.invalid_nextflow_config_default_parameters) > 0: + log.info( + "[red][✗] Invalid default parameters found:\n --{}\n\nNOTE: Use null in config for no default.".format( + "\n --".join( + [ + f"{param}: {msg}" + for param, msg in self.invalid_nextflow_config_default_parameters.items() + ] + ) + ) + ) + else: + log.info("[green][✓] Pipeline schema looks valid[/] [dim](found {} params)".format(num_params)) except json.decoder.JSONDecodeError as e: error_msg = "[bold red]Could not parse schema JSON:[/] {}".format(e) log.error(error_msg) @@ -249,7 +261,7 @@ def validate_default_params(self): param, group_properties[param]["type"], self.pipeline_params[param] ) else: - self.invalid_nextflow_config_default_parameters.append(param) + self.invalid_nextflow_config_default_parameters[param] = "Not in pipeline parameters" # Go over ungrouped params if any exist ungrouped_properties = self.schema.get("properties") @@ -262,7 +274,7 @@ def validate_default_params(self): param, ungrouped_properties[param]["type"], self.pipeline_params[param] ) else: - self.invalid_nextflow_config_default_parameters.append(param) + self.invalid_nextflow_config_default_parameters[param] = "Not in pipeline parameters" def validate_config_default_parameter(self, param, schema_default_type, config_default): """ @@ -274,21 +286,29 @@ def validate_config_default_parameter(self, param, schema_default_type, config_d return # else check for allowed defaults if schema_default_type == "string": - if config_default in ["false", "true", "''"]: - self.invalid_nextflow_config_default_parameters.append(param) + if str(config_default) in ["false", "true", "''"]: + self.invalid_nextflow_config_default_parameters[ + param + ] = f"String should not be set to `{config_default}`" if schema_default_type == "boolean": - if not config_default in ["false", "true"]: - self.invalid_nextflow_config_default_parameters.append(param) + if not str(config_default) in ["false", "true"]: + self.invalid_nextflow_config_default_parameters[ + param + ] = f"Booleans should only be true or false, not `{config_default}`" if schema_default_type == "integer": try: int(config_default) except ValueError: - self.invalid_nextflow_config_default_parameters.append(param) + self.invalid_nextflow_config_default_parameters[ + param + ] = f"Does not look like an integer: `{config_default}`" if schema_default_type == "number": try: float(config_default) except ValueError: - self.invalid_nextflow_config_default_parameters.append(param) + self.invalid_nextflow_config_default_parameters[ + param + ] = f"Does not look like a number (float): `{config_default}`" def validate_schema(self, schema=None): """ @@ -577,7 +597,7 @@ def add_schema_found_configs(self): ): if "properties" not in self.schema: self.schema["properties"] = {} - self.schema["properties"][p_key] = self.build_schema_param(p_val) + self.schema["properties"][p_key] = self.load_lint_schema(p_val) log.debug("Adding '{}' to pipeline schema".format(p_key)) params_added.append(p_key) @@ -603,18 +623,13 @@ def build_schema_param(self, p_val): if p_val == "null": p_val = None - # NB: Only test "True" for booleans, as it is very common to initialise - # an empty param as false when really we expect a string at a later date.. - if p_val == "True": - p_val = True + # Booleans + if p_val == "True" or p_val == "False": + p_val = p_val == "True" # Convert to bool p_type = "boolean" p_schema = {"type": p_type, "default": p_val} - # Assume that false and empty strings shouldn't be a default - if p_val == "false" or p_val == "" or p_val is None: - del p_schema["default"] - return p_schema def launch_web_builder(self): From 102cc8b498aeb98ce07fa318f61d4e9c6dd70267 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Wed, 17 Nov 2021 00:04:38 +0100 Subject: [PATCH 131/266] Template schema lib - show default if it's not null, even if falsey --- nf_core/pipeline-template/lib/NfcoreSchema.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/lib/NfcoreSchema.groovy b/nf_core/pipeline-template/lib/NfcoreSchema.groovy index 8d6920dd64..a168c3f16a 100755 --- a/nf_core/pipeline-template/lib/NfcoreSchema.groovy +++ b/nf_core/pipeline-template/lib/NfcoreSchema.groovy @@ -202,7 +202,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 From a1836fe8b596de38907557fb0567002c635e5f10 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Wed, 17 Nov 2021 00:08:26 +0100 Subject: [PATCH 132/266] Revert accidental copy paste replacement. Thanks pytests\! --- nf_core/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/schema.py b/nf_core/schema.py index 7128a03bc8..4b7c35293b 100644 --- a/nf_core/schema.py +++ b/nf_core/schema.py @@ -597,7 +597,7 @@ def add_schema_found_configs(self): ): if "properties" not in self.schema: self.schema["properties"] = {} - self.schema["properties"][p_key] = self.load_lint_schema(p_val) + self.schema["properties"][p_key] = self.build_schema_param(p_val) log.debug("Adding '{}' to pipeline schema".format(p_key)) params_added.append(p_key) From 738fb12c2c1b7d3e021aafefc2b4c7265754fbfd Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Wed, 17 Nov 2021 00:14:06 +0100 Subject: [PATCH 133/266] Move changelog to correct section --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64eaf064ba..9f6edeb1a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ ### General +* Made lint check for parameters defaults stricter [[#992](https://github.com/nf-core/tools/issues/992)] + * Defaults must now match the variable type specified in the schema + * If you want the parameter to not have a default value, use `null` + * Strings set to `false` or an empty string in `nextflow.config` will now fail linting * Changed `questionary` `ask()` to `unsafe_ask()` to not catch `KeyboardInterupts` ([#1237](https://github.com/nf-core/tools/issues/1237)) * Fixed bug in `nf-core launch` due to revisions specified with `-r` not being added to nextflow command. ([#1246](https://github.com/nf-core/tools/issues/1246)) * Update regex in `readme` test of `nf-core lint` to agree with the pipeline template ([#1260](https://github.com/nf-core/tools/issues/1260)) @@ -83,7 +87,6 @@ This marks the first Nextflow DSL2-centric release of `tools` which means that s * Regular release sync fix - this time it was to do with JSON serialisation [[#1072](https://github.com/nf-core/tools/pull/1072)] * Fixed bug in schema validation that ignores upper/lower-case typos in parameters [[#1087](https://github.com/nf-core/tools/issues/1087)] * Bugfix: Download should use path relative to workflow for configs -* Added lint check for valid default parameters in `nextflow.config` [[#992](https://github.com/nf-core/tools/issues/992)] * Remove lint checks for files related to conda and docker as not needed anymore for DSL2 * Removed `params_used` lint check because of incompatibility with DSL2 * Added`modules bump-versions` command to `README.md` From 373fe2904dbdb15d5491016b308259d77fbb656c Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Wed, 17 Nov 2021 00:26:35 +0100 Subject: [PATCH 134/266] Update lint test files_exist for new filenames --- nf_core/lint/files_exist.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/nf_core/lint/files_exist.py b/nf_core/lint/files_exist.py index 41365b1094..f2195a28ab 100644 --- a/nf_core/lint/files_exist.py +++ b/nf_core/lint/files_exist.py @@ -23,9 +23,9 @@ def files_exist(self): .markdownlint.yml .github/.dockstore.yml .github/CONTRIBUTING.md - .github/ISSUE_TEMPLATE/bug_report.md + .github/ISSUE_TEMPLATE/bug_report.yml .github/ISSUE_TEMPLATE/config.yml - .github/ISSUE_TEMPLATE/feature_request.md + .github/ISSUE_TEMPLATE/feature_request.yml .github/PULL_REQUEST_TEMPLATE.md .github/workflows/branch.yml .github/workflows/ci.yml @@ -76,6 +76,7 @@ def files_exist(self): bin/markdown_to_html.r conf/aws.config .github/workflows/push_dockerhub.yml + .github/ISSUE_TEMPLATE/bug_report.md Files that *should not* be present: @@ -107,9 +108,9 @@ def files_exist(self): ["README.md"], [os.path.join(".github", ".dockstore.yml")], [os.path.join(".github", "CONTRIBUTING.md")], - [os.path.join(".github", "ISSUE_TEMPLATE", "bug_report.md")], + [os.path.join(".github", "ISSUE_TEMPLATE", "bug_report.yml")], [os.path.join(".github", "ISSUE_TEMPLATE", "config.yml")], - [os.path.join(".github", "ISSUE_TEMPLATE", "feature_request.md")], + [os.path.join(".github", "ISSUE_TEMPLATE", "feature_request.yml")], [os.path.join(".github", "PULL_REQUEST_TEMPLATE.md")], [os.path.join(".github", "workflows", "branch.yml")], [os.path.join(".github", "workflows", "ci.yml")], @@ -152,6 +153,8 @@ def files_exist(self): os.path.join("bin", "markdown_to_html.r"), os.path.join("conf", "aws.config"), os.path.join(".github", "workflows", "push_dockerhub.yml"), + [os.path.join(".github", "ISSUE_TEMPLATE", "bug_report.md")], + [os.path.join(".github", "ISSUE_TEMPLATE", "feature_request.md")], ] files_warn_ifexists = [".travis.yml"] From 70e0cc7dd1c68328f2a857ab734731d0db7a227d Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Wed, 17 Nov 2021 00:28:41 +0100 Subject: [PATCH 135/266] Fix copy paste error --- nf_core/lint/files_exist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/lint/files_exist.py b/nf_core/lint/files_exist.py index f2195a28ab..cb7d5f978c 100644 --- a/nf_core/lint/files_exist.py +++ b/nf_core/lint/files_exist.py @@ -153,8 +153,8 @@ def files_exist(self): os.path.join("bin", "markdown_to_html.r"), os.path.join("conf", "aws.config"), os.path.join(".github", "workflows", "push_dockerhub.yml"), - [os.path.join(".github", "ISSUE_TEMPLATE", "bug_report.md")], - [os.path.join(".github", "ISSUE_TEMPLATE", "feature_request.md")], + os.path.join(".github", "ISSUE_TEMPLATE", "bug_report.md"), + os.path.join(".github", "ISSUE_TEMPLATE", "feature_request.md"), ] files_warn_ifexists = [".travis.yml"] From 690ee2f67991fb4d24520fbc1ca44e98725609a5 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Wed, 17 Nov 2021 00:29:46 +0100 Subject: [PATCH 136/266] Rerun black --- tests/test_schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 6eb3dbc08e..a15bb07be5 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -127,12 +127,12 @@ def test_validate_params_pass(self): self.schema_obj.load_schema() self.schema_obj.input_params = {"input": "fubar.csv"} assert self.schema_obj.validate_params() - + def test_validate_params_pass_ext(self): """Try validating an extended set of parameters against a schema""" self.schema_obj.schema = { "properties": {"foo": {"type": "string"}, "bar": {"type": "string", "default": ""}}, - "required": ["foo"] + "required": ["foo"], } assert self.schema_obj.validate_params() From 060a38c2e47f263cf388f4bb9dd365c448625743 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Wed, 17 Nov 2021 00:31:08 +0100 Subject: [PATCH 137/266] Update lint test: files_unchanged --- nf_core/lint/files_unchanged.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nf_core/lint/files_unchanged.py b/nf_core/lint/files_unchanged.py index 262e8c4449..39842ae32c 100644 --- a/nf_core/lint/files_unchanged.py +++ b/nf_core/lint/files_unchanged.py @@ -22,9 +22,9 @@ def files_unchanged(self): .markdownlint.yml .github/.dockstore.yml .github/CONTRIBUTING.md - .github/ISSUE_TEMPLATE/bug_report.md + .github/ISSUE_TEMPLATE/bug_report.yml .github/ISSUE_TEMPLATE/config.yml - .github/ISSUE_TEMPLATE/feature_request.md + .github/ISSUE_TEMPLATE/feature_request.yml .github/PULL_REQUEST_TEMPLATE.md .github/workflows/branch.yml .github/workflows/linting_comment.yml @@ -81,9 +81,9 @@ def files_unchanged(self): ["LICENSE", "LICENSE.md", "LICENCE", "LICENCE.md"], # NB: British / American spelling [os.path.join(".github", ".dockstore.yml")], [os.path.join(".github", "CONTRIBUTING.md")], - [os.path.join(".github", "ISSUE_TEMPLATE", "bug_report.md")], + [os.path.join(".github", "ISSUE_TEMPLATE", "bug_report.yml")], [os.path.join(".github", "ISSUE_TEMPLATE", "config.yml")], - [os.path.join(".github", "ISSUE_TEMPLATE", "feature_request.md")], + [os.path.join(".github", "ISSUE_TEMPLATE", "feature_request.yml")], [os.path.join(".github", "PULL_REQUEST_TEMPLATE.md")], [os.path.join(".github", "workflows", "branch.yml")], [os.path.join(".github", "workflows", "linting_comment.yml")], From 0774214acf0dfa38c6b93c99eb6d7f8377699300 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Wed, 17 Nov 2021 00:35:48 +0100 Subject: [PATCH 138/266] Remove new test --- tests/test_schema.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index a15bb07be5..175b23880c 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -128,14 +128,6 @@ def test_validate_params_pass(self): self.schema_obj.input_params = {"input": "fubar.csv"} assert self.schema_obj.validate_params() - def test_validate_params_pass_ext(self): - """Try validating an extended set of parameters against a schema""" - self.schema_obj.schema = { - "properties": {"foo": {"type": "string"}, "bar": {"type": "string", "default": ""}}, - "required": ["foo"], - } - assert self.schema_obj.validate_params() - def test_validate_params_fail(self): """Check that False is returned if params don't validate against a schema""" # Load the template schema From 012acbd4debdbecfc40683c9ff432ee6320b53c1 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Wed, 17 Nov 2021 00:37:44 +0100 Subject: [PATCH 139/266] Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 727077c569..a7eb79351c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Update `dumpsoftwareversion` module to correctly report versions with trailing zeros. * Remove `params.hostnames` from the pipeline template ([#1304](https://github.com/nf-core/tools/issues/1304)) * Update `.gitattributes` to mark installed modules and subworkflows as `linguist-generated` ([#1311](https://github.com/nf-core/tools/issues/1311)) +* New YAML issue templates for pipeline bug reports and feature requests, with a much richer interface ([#1165](https://github.com/nf-core/tools/pull/1165)) ### General @@ -18,6 +19,7 @@ * Update 'fix' message in `nf-core lint` to conform to the current command line options. ([#1259](https://github.com/nf-core/tools/issues/1259)) * Fixed bug in `nf-core list` when `NXF_HOME` is set * Run CI test used to create and lint/run the pipeline template with minimum and latest edge release of NF ([#1304](https://github.com/nf-core/tools/issues/1304)) +* New YAML issue templates for tools bug reports and feature requests, with a much richer interface ([#1165](https://github.com/nf-core/tools/pull/1165)) ### Modules From a135b8e93d14b98be09dec1514e6e348a56df49f Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Wed, 17 Nov 2021 00:50:40 +0100 Subject: [PATCH 140/266] Print stdout as well as stderr on nextflow command errors. Update changelog --- CHANGELOG.md | 1 + nf_core/utils.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7eb79351c..ac8691d05a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * Fixed bug in `nf-core list` when `NXF_HOME` is set * Run CI test used to create and lint/run the pipeline template with minimum and latest edge release of NF ([#1304](https://github.com/nf-core/tools/issues/1304)) * New YAML issue templates for tools bug reports and feature requests, with a much richer interface ([#1165](https://github.com/nf-core/tools/pull/1165)) +* Handle synax errors in Nextflow config nicely when running `nf-core schema build` ([#1267](https://github.com/nf-core/tools/pull/1267)) ### Modules diff --git a/nf_core/utils.py b/nf_core/utils.py index 55751ae341..46320cd092 100644 --- a/nf_core/utils.py +++ b/nf_core/utils.py @@ -297,7 +297,7 @@ def nextflow_cmd(cmd): raise AssertionError("It looks like Nextflow is not installed. It is required for most nf-core functions.") except subprocess.CalledProcessError as e: raise AssertionError( - f"Command '{cmd}' returned non-zero error code '{e.returncode}':\n[red]> {e.stderr.decode()}" + f"Command '{cmd}' returned non-zero error code '{e.returncode}':\n[red]> {e.stderr.decode()}{e.stdout.decode()}" ) From 68e9915b2bd1bfc7de31e4f050c1253e082b6522 Mon Sep 17 00:00:00 2001 From: "Robert A. Petit III" Date: Wed, 17 Nov 2021 09:07:49 -0700 Subject: [PATCH 141/266] fix get_repo_type --- nf_core/modules/module_utils.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nf_core/modules/module_utils.py b/nf_core/modules/module_utils.py index 30d6e4cc08..6ef675bf95 100644 --- a/nf_core/modules/module_utils.py +++ b/nf_core/modules/module_utils.py @@ -344,10 +344,12 @@ def get_repo_type(dir): raise LookupError("Could not find directory: {}".format(dir)) # Determine repository type - if os.path.exists(os.path.join(dir, "main.nf")): - return "pipeline" - elif os.path.exists(os.path.join(dir, "modules")): - return "modules" + if os.path.exists("README.md"): + with open("README.md") as fh: + if fh.readline().rstrip().startswith("# ![nf-core/modules]"): + return "modules" + else: + return "pipeline" else: raise LookupError("Could not determine repository type of '{}'".format(dir)) From ad5f91f8d5b585b8edeb1a8ddc2c7f75b1055457 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 18 Nov 2021 15:38:19 +0100 Subject: [PATCH 142/266] pipeline readme - docs tweak for config profile quickstart Trying to make the quickstart section easier to read, as we keep having people running the pipelines with just `-profile test`. * Avoid using `` etc as that is less clear that it's required * Avoid very long string of different container technologies * Minor tweaks of text --- nf_core/pipeline-template/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nf_core/pipeline-template/README.md b/nf_core/pipeline-template/README.md index c502f2ea50..dfb8baeb37 100644 --- a/nf_core/pipeline-template/README.md +++ b/nf_core/pipeline-template/README.md @@ -40,11 +40,14 @@ On release, automated continuous integration tests run the pipeline on a full-si 3. Download the pipeline and test it on a minimal dataset with a single command: ```console - nextflow run {{ name }} -profile test, + nextflow run {{ name }} -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! From bf46dc55d5016b7c53d0a970490ae1c253853bcf Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 18 Nov 2021 23:38:06 +0100 Subject: [PATCH 143/266] Update AWS test for v2 syntax of the tower-action --- nf_core/pipeline-template/.github/workflows/awstest.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nf_core/pipeline-template/.github/workflows/awstest.yml b/nf_core/pipeline-template/.github/workflows/awstest.yml index fff75db64c..3ef7d2b93d 100644 --- a/nf_core/pipeline-template/.github/workflows/awstest.yml +++ b/nf_core/pipeline-template/.github/workflows/awstest.yml @@ -11,11 +11,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Launch workflow via tower - uses: nf-core/tower-action@master + uses: nf-core/tower-action@v2 {% raw %} 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 }} @@ -24,5 +24,5 @@ jobs: { "outdir": "s3://{% raw %}${{ secrets.AWS_S3_BUCKET }}{% endraw %}/{{ short_name }}/{% raw %}results-${{ github.sha }}{% endraw %}" } - profiles: '[ "test", "aws_tower" ]' + profiles: test,aws_tower From ce9c73c01fb2e26d6307dfcda1b03062c86f0616 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 18 Nov 2021 23:39:41 +0100 Subject: [PATCH 144/266] Update AWS full test to use v2 tower action syntax --- nf_core/pipeline-template/.github/workflows/awsfulltest.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nf_core/pipeline-template/.github/workflows/awsfulltest.yml b/nf_core/pipeline-template/.github/workflows/awsfulltest.yml index 9ec98b29f9..2b83987597 100644 --- a/nf_core/pipeline-template/.github/workflows/awsfulltest.yml +++ b/nf_core/pipeline-template/.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 {% raw %} 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://{% raw %}${{ secrets.AWS_S3_BUCKET }}{% endraw %}/{{ short_name }}/{% raw %}results-${{ github.sha }}{% endraw %}" } - profiles: '[ "test_full", "aws_tower" ]' + profiles: test_full,aws_tower From 698cb1fa99ba351acbbb932b751a492e2e7dc272 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 18 Nov 2021 23:40:34 +0100 Subject: [PATCH 145/266] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddd680bd76..fcd034f39c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Remove `params.hostnames` from the pipeline template ([#1304](https://github.com/nf-core/tools/issues/1304)) * Update `.gitattributes` to mark installed modules and subworkflows as `linguist-generated` ([#1311](https://github.com/nf-core/tools/issues/1311)) * New YAML issue templates for pipeline bug reports and feature requests, with a much richer interface ([#1165](https://github.com/nf-core/tools/pull/1165)) +* Update AWS test GitHub Actions to use v2 of [nf-core/tower-action](https://github.com/nf-core/tower-action) ### General From cdda843c902f494263545fd54ca4a92919afc43a Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 19 Nov 2021 20:07:31 +0100 Subject: [PATCH 146/266] Update nf_core/pipeline-template/README.md --- nf_core/pipeline-template/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/nf_core/pipeline-template/README.md b/nf_core/pipeline-template/README.md index dfb8baeb37..3a837f431a 100644 --- a/nf_core/pipeline-template/README.md +++ b/nf_core/pipeline-template/README.md @@ -42,7 +42,6 @@ On release, automated continuous integration tests run the pipeline on a full-si ```console nextflow run {{ name }} -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`. From d928c0a745fe18896a6c60df4654fbd2dd2a4af0 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 19 Nov 2021 20:08:27 +0100 Subject: [PATCH 147/266] Apply suggestions from code review --- nf_core/pipeline-template/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/nf_core/pipeline-template/README.md b/nf_core/pipeline-template/README.md index 3a837f431a..6003672ebc 100644 --- a/nf_core/pipeline-template/README.md +++ b/nf_core/pipeline-template/README.md @@ -42,6 +42,7 @@ On release, automated continuous integration tests run the pipeline on a full-si ```console nextflow run {{ name }} -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`. From d08a2afdcbaee9f4654fe6c5a1ac9a434011dbb0 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Mon, 22 Nov 2021 16:16:50 +0100 Subject: [PATCH 148/266] Update modules template to DSL2 v2.0 syntax --- CHANGELOG.md | 1 + nf_core/module-template/modules/functions.nf | 78 -------------------- nf_core/module-template/modules/main.nf | 24 ++---- nf_core/modules/create.py | 2 - 4 files changed, 9 insertions(+), 96 deletions(-) delete mode 100644 nf_core/module-template/modules/functions.nf diff --git a/CHANGELOG.md b/CHANGELOG.md index fcd034f39c..06410ba0f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ * Added `--diff` flag to `nf-core modules update` which shows the diff between the installed files and the versions * Update `nf-core modules create` help texts which were not changed with the introduction of the `--dir` flag * Check if README is from modules repo +* Update module template to DSL2 v2.0 (remove `functions.nf` from modules template and updating `main.nf` ([#1289](https://github.com/nf-core/tools/pull/)) ## [v2.1 - Zinc Zebra](https://github.com/nf-core/tools/releases/tag/2.1) - [2021-07-27] diff --git a/nf_core/module-template/modules/functions.nf b/nf_core/module-template/modules/functions.nf deleted file mode 100644 index 85628ee0eb..0000000000 --- a/nf_core/module-template/modules/functions.nf +++ /dev/null @@ -1,78 +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() -} - -// -// Extract name of module from process name using $task.process -// -def getProcessName(task_process) { - return task_process.tokenize(':')[-1] -} - -// -// 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) { - def ioptions = initOptions(args.options) - def path_list = [ ioptions.publish_dir ?: args.publish_dir ] - - // Do not publish versions.yml unless running from pytest workflow - if (args.filename.equals('versions.yml') && !System.getenv("NF_CORE_MODULES_TEST")) { - return null - } - 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/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 8b4360e3de..ee28a4a117 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -1,14 +1,12 @@ -// Import generic module functions -include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' - // TODO nf-core: If in doubt look at other nf-core/modules to see how we are doing things! :) // https://github.com/nf-core/modules/tree/master/modules // You can also ask for help via your pull request or on the #modules channel on the nf-core Slack workspace: // https://nf-co.re/join // TODO nf-core: A module file SHOULD only define input and output files as command-line parameters. -// All other parameters MUST be provided as a string i.e. "options.args" -// where "params.options" is a Groovy Map that MUST be provided via the addParams section of the including workflow. +// All other parameters MUST be provided using "process.ext" directive, see here: +// https://www.nextflow.io/docs/latest/process.html#ext +// where "process.ext" is a Groovy Map that MUST be provided via the modules.config file. // Any parameters that need to be evaluated in the context of a particular sample // e.g. single-end/paired-end data MUST also be defined and evaluated appropriately. // TODO nf-core: Software that can be piped together SHOULD be added to separate module files @@ -17,17 +15,10 @@ include { initOptions; saveFiles; getSoftwareName; getProcessName } from './func // bwa mem | samtools view -B -T ref.fasta // TODO nf-core: Optional inputs are not currently supported by Nextflow. However, using an empty // list (`[]`) instead of a file can be used to work around this issue. - -params.options = [:] -options = initOptions(params.options) - process {{ tool_name_underscore|upper }} { tag {{ '"$meta.id"' if has_meta else "'$bam'" }} label '{{ process_label }}' - publishDir "${params.outdir}", - mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:getSoftwareName(task.process), meta:{{ 'meta' if has_meta else "[:]" }}, publish_by_meta:{{ "['id']" if has_meta else "[]" }}) } - + // TODO nf-core: List required Conda package(s). // Software MUST be pinned to channel (i.e. "bioconda"), version (i.e. "1.10"). // For Conda, the build (i.e. "h9402c20_2") must be EXCLUDED to support installation on different operating systems. @@ -56,13 +47,14 @@ process {{ tool_name_underscore|upper }} { script: {% if has_meta -%} - def prefix = options.suffix ? "${meta.id}${options.suffix}" : "${meta.id}" + def prefix = task.ext.suffix ? "${meta.id}${task.ext.suffix}" : "${meta.id}" {%- endif %} + def args = task.ext.args ?: '' // TODO nf-core: Where possible, a command MUST be provided to obtain the version number of the software e.g. 1.10 // If the software is unable to output a version number on the command-line then it can be manually specified // e.g. https://github.com/nf-core/modules/blob/master/modules/homer/annotatepeaks/main.nf // Each software used MUST provide the software name and version number in the YAML version file (versions.yml) - // TODO nf-core: It MUST be possible to pass additional parameters to the tool as a command-line string via the "$options.args" variable + // TODO nf-core: It MUST be possible to pass additional parameters to the tool as a command-line string via the "process.ext.args" directive // TODO nf-core: If the tool supports multi-threading then you MUST provide the appropriate parameter // using the Nextflow "task" variable e.g. "--threads $task.cpus" // TODO nf-core: Please replace the example samtools command below with your module's command @@ -70,7 +62,7 @@ process {{ tool_name_underscore|upper }} { """ samtools \\ sort \\ - $options.args \\ + $args \\ -@ $task.cpus \\ {%- if has_meta %} -o ${prefix}.bam \\ diff --git a/nf_core/modules/create.py b/nf_core/modules/create.py index 3b2b200abf..9c0a0bf7ba 100644 --- a/nf_core/modules/create.py +++ b/nf_core/modules/create.py @@ -63,7 +63,6 @@ def create(self): modules/modules/tool/subtool/ * main.nf * meta.yml - * functions.nf modules/tests/modules/tool/subtool/ * main.nf * test.yml @@ -355,7 +354,6 @@ def get_module_dirs(self): ) # Set file paths - can be tool/ or tool/subtool/ so can't do in template directory structure - file_paths[os.path.join("modules", "functions.nf")] = os.path.join(software_dir, "functions.nf") file_paths[os.path.join("modules", "main.nf")] = os.path.join(software_dir, "main.nf") file_paths[os.path.join("modules", "meta.yml")] = os.path.join(software_dir, "meta.yml") file_paths[os.path.join("tests", "main.nf")] = os.path.join(test_dir, "main.nf") From 78b0d8a4fa35a12ac6f0de6b35b1b783941ad4e2 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Mon, 22 Nov 2021 17:20:08 +0100 Subject: [PATCH 149/266] Update lint for modules for DSL2 v2.0 (some code just commented, WIP) --- nf_core/modules/lint/__init__.py | 6 +- nf_core/modules/lint/functions_nf.py | 98 ++++++++++++++-------------- nf_core/modules/lint/main_nf.py | 56 ++++++++-------- nf_core/modules/module_utils.py | 6 +- nf_core/modules/nfcore_module.py | 1 - 5 files changed, 82 insertions(+), 85 deletions(-) diff --git a/nf_core/modules/lint/__init__.py b/nf_core/modules/lint/__init__.py index f9de48a304..50a6a2a573 100644 --- a/nf_core/modules/lint/__init__.py +++ b/nf_core/modules/lint/__init__.py @@ -61,7 +61,6 @@ class ModuleLint(ModuleCommand): # Import lint functions from .main_nf import main_nf - from .functions_nf import functions_nf from .meta_yml import meta_yml from .module_changes import module_changes from .module_tests import module_tests @@ -96,7 +95,7 @@ def __init__(self, dir): @staticmethod def _get_all_lint_tests(): - return ["main_nf", "functions_nf", "meta_yml", "module_changes", "module_todos"] + return ["main_nf", "meta_yml", "module_changes", "module_todos"] def lint(self, module=None, key=(), all_modules=False, print_results=True, show_passed=False, local=False): """ @@ -231,7 +230,7 @@ def get_installed_modules(self): # Filter local modules if os.path.exists(local_modules_dir): local_modules = os.listdir(local_modules_dir) - local_modules = sorted([x for x in local_modules if (x.endswith(".nf") and not x == "functions.nf")]) + local_modules = sorted([x for x in local_modules if x.endswith(".nf")]) # nf-core/modules if self.repo_type == "modules": @@ -309,7 +308,6 @@ def lint_module(self, mod, local=False): If the module is a nf-core module we check for existence of the files - main.nf - meta.yml - - functions.nf And verify that their content conform to the nf-core standards. If the linting is run for modules in the central nf-core/modules repo diff --git a/nf_core/modules/lint/functions_nf.py b/nf_core/modules/lint/functions_nf.py index aef0d115ea..648b1c3173 100644 --- a/nf_core/modules/lint/functions_nf.py +++ b/nf_core/modules/lint/functions_nf.py @@ -1,56 +1,56 @@ -#!/usr/bin/env python -import logging -import os -import nf_core +# #!/usr/bin/env python +# import logging +# import os +# import nf_core -log = logging.getLogger(__name__) +# log = logging.getLogger(__name__) -def functions_nf(module_lint_object, module): - """ - Lint a functions.nf file - Verifies that the file exists and contains all necessary functions - """ - local_copy = None - template_copy = None - try: - with open(module.function_nf, "r") as fh: - lines = fh.readlines() - module.passed.append(("functions_nf_exists", "'functions.nf' exists", module.function_nf)) - except FileNotFoundError as e: - module.failed.append(("functions_nf_exists", "'functions.nf' does not exist", module.function_nf)) - return +# def functions_nf(module_lint_object, module): +# """ +# Lint a functions.nf file +# Verifies that the file exists and contains all necessary functions +# """ +# local_copy = None +# template_copy = None +# try: +# with open(module.function_nf, "r") as fh: +# lines = fh.readlines() +# module.passed.append(("functions_nf_exists", "'functions.nf' exists", module.function_nf)) +# except FileNotFoundError as e: +# module.failed.append(("functions_nf_exists", "'functions.nf' does not exist", module.function_nf)) +# return - # Test whether all required functions are present - required_functions = ["getSoftwareName", "getProcessName", "initOptions", "getPathFromList", "saveFiles"] - lines = "\n".join(lines) - contains_all_functions = True - for f in required_functions: - if not "def " + f in lines: - module.failed.append(("functions_nf_func_exist", "Function is missing: `{f}`", module.function_nf)) - contains_all_functions = False - if contains_all_functions: - module.passed.append(("functions_nf_func_exist", "All functions present", module.function_nf)) +# # Test whether all required functions are present +# required_functions = ["getSoftwareName", "getProcessName", "initOptions", "getPathFromList", "saveFiles"] +# lines = "\n".join(lines) +# contains_all_functions = True +# for f in required_functions: +# if not "def " + f in lines: +# module.failed.append(("functions_nf_func_exist", "Function is missing: `{f}`", module.function_nf)) +# contains_all_functions = False +# if contains_all_functions: +# module.passed.append(("functions_nf_func_exist", "All functions present", module.function_nf)) - # Compare functions.nf file to the most recent template - # Get file content of the module functions.nf - try: - local_copy = open(module.function_nf, "r").read() - except FileNotFoundError as e: - log.error(f"Could not open {module.function_nf}") +# # Compare functions.nf file to the most recent template +# # Get file content of the module functions.nf +# try: +# local_copy = open(module.function_nf, "r").read() +# except FileNotFoundError as e: +# log.error(f"Could not open {module.function_nf}") - # Get the template file - template_copy_path = os.path.join(os.path.dirname(nf_core.__file__), "module-template/modules/functions.nf") - try: - template_copy = open(template_copy_path, "r").read() - except FileNotFoundError as e: - log.error(f"Could not open {template_copy_path}") +# # Get the template file +# template_copy_path = os.path.join(os.path.dirname(nf_core.__file__), "module-template/modules/functions.nf") +# try: +# template_copy = open(template_copy_path, "r").read() +# except FileNotFoundError as e: +# log.error(f"Could not open {template_copy_path}") - # Compare the files - if local_copy and template_copy: - if local_copy != template_copy: - module.failed.append( - ("function_nf_comparison", "New version of functions.nf available", module.function_nf) - ) - else: - module.passed.append(("function_nf_comparison", "functions.nf is up to date", module.function_nf)) +# # Compare the files +# if local_copy and template_copy: +# if local_copy != template_copy: +# module.failed.append( +# ("function_nf_comparison", "New version of functions.nf available", module.function_nf) +# ) +# else: +# module.passed.append(("function_nf_comparison", "functions.nf is up to date", module.function_nf)) diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index 4b63295c20..8101472dfb 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -27,12 +27,12 @@ def main_nf(module_lint_object, module): return # Check that options are defined - initoptions_re = re.compile(r"\s*options\s*=\s*initOptions\s*\(\s*params\.options\s*\)\s*") - paramsoptions_re = re.compile(r"\s*params\.options\s*=\s*\[:\]\s*") - if any(initoptions_re.match(l) for l in lines) and any(paramsoptions_re.match(l) for l in lines): - module.passed.append(("main_nf_options", "'options' variable specified", module.main_nf)) - else: - module.warned.append(("main_nf_options", "'options' variable not specified", module.main_nf)) + # initoptions_re = re.compile(r"\s*options\s*=\s*initOptions\s*\(\s*params\.options\s*\)\s*") + # paramsoptions_re = re.compile(r"\s*params\.options\s*=\s*\[:\]\s*") + # if any(initoptions_re.match(l) for l in lines) and any(paramsoptions_re.match(l) for l in lines): + # module.passed.append(("main_nf_options", "'options' variable specified", module.main_nf)) + # else: + # module.warned.append(("main_nf_options", "'options' variable not specified", module.main_nf)) # Go through module main.nf file and switch state according to current section # Perform section-specific linting @@ -81,28 +81,28 @@ def main_nf(module_lint_object, module): module.failed.append(("main_nf_meta_output", "'meta' map not emitted in output channel(s)", module.main_nf)) # if meta is specified, it should also be used as "saveAs ... meta:meta, publish_by_meta:['id']" - save_as = [pl for pl in process_lines if "saveAs" in pl] - if len(save_as) > 0 and re.search("\s*meta\s*:\s*meta", save_as[0]): - module.passed.append(("main_nf_meta_saveas", "'meta:meta' specified in saveAs function", module.main_nf)) - else: - module.failed.append(("main_nf_meta_saveas", "'meta:meta' unspecified in saveAs function", module.main_nf)) - - if len(save_as) > 0 and re.search("\s*publish_by_meta\s*:\s*\['id'\]", save_as[0]): - module.passed.append( - ( - "main_nf_publish_meta_saveas", - "'publish_by_meta:['id']' specified in saveAs function", - module.main_nf, - ) - ) - else: - module.failed.append( - ( - "main_nf_publish_meta_saveas", - "'publish_by_meta:['id']' unspecified in saveAs function", - module.main_nf, - ) - ) + # save_as = [pl for pl in process_lines if "saveAs" in pl] + # if len(save_as) > 0 and re.search("\s*meta\s*:\s*meta", save_as[0]): + # module.passed.append(("main_nf_meta_saveas", "'meta:meta' specified in saveAs function", module.main_nf)) + # else: + # module.failed.append(("main_nf_meta_saveas", "'meta:meta' unspecified in saveAs function", module.main_nf)) + + # if len(save_as) > 0 and re.search("\s*publish_by_meta\s*:\s*\['id'\]", save_as[0]): + # module.passed.append( + # ( + # "main_nf_publish_meta_saveas", + # "'publish_by_meta:['id']' specified in saveAs function", + # module.main_nf, + # ) + # ) + # else: + # module.failed.append( + # ( + # "main_nf_publish_meta_saveas", + # "'publish_by_meta:['id']' unspecified in saveAs function", + # module.main_nf, + # ) + # ) # Check that a software version is emitted if "version" in outputs: diff --git a/nf_core/modules/module_utils.py b/nf_core/modules/module_utils.py index 6ef675bf95..e14afd795b 100644 --- a/nf_core/modules/module_utils.py +++ b/nf_core/modules/module_utils.py @@ -225,7 +225,7 @@ def iterate_commit_log_page(module_name, module_path, modules_repo, commit_shas) are identical to remote files """ - files_to_check = ["main.nf", "functions.nf", "meta.yml"] + files_to_check = ["main.nf", "meta.yml"] local_file_contents = [None, None, None] for i, file in enumerate(files_to_check): try: @@ -251,7 +251,7 @@ def local_module_equal_to_commit(local_files, module_name, modules_repo, commit_ bool: Whether all local files are identical to remote version """ - files_to_check = ["main.nf", "functions.nf", "meta.yml"] + files_to_check = ["main.nf", "meta.yml"] files_are_equal = [False, False, False] remote_copies = [None, None, None] @@ -304,7 +304,7 @@ def get_installed_modules(dir, repo_type="modules"): # Filter local modules if os.path.exists(local_modules_dir): local_modules = os.listdir(local_modules_dir) - local_modules = sorted([x for x in local_modules if (x.endswith(".nf") and not x == "functions.nf")]) + local_modules = sorted([x for x in local_modules if x.endswith(".nf")]) # nf-core/modules if repo_type == "modules": diff --git a/nf_core/modules/nfcore_module.py b/nf_core/modules/nfcore_module.py index e6e490febd..f828142fda 100644 --- a/nf_core/modules/nfcore_module.py +++ b/nf_core/modules/nfcore_module.py @@ -26,7 +26,6 @@ def __init__(self, module_dir, repo_type, base_dir, nf_core_module=True): # Initialize the important files self.main_nf = os.path.join(self.module_dir, "main.nf") self.meta_yml = os.path.join(self.module_dir, "meta.yml") - self.function_nf = os.path.join(self.module_dir, "functions.nf") if self.repo_type == "pipeline": self.module_name = module_dir.split("nf-core/modules" + os.sep)[1] else: From dc361b09cb380fdc2e42dee5827507c580bec9e3 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Mon, 22 Nov 2021 17:30:25 +0100 Subject: [PATCH 150/266] Update "nf-core modules create" prompt --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c874796049..e440979bde 100644 --- a/README.md +++ b/README.md @@ -1103,7 +1103,7 @@ $ nf-core modules create | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 INFO Press enter to use default values (shown in brackets) or type your own responses. ctrl+click underlined text to open links. @@ -1118,12 +1118,11 @@ INFO Where applicable all sample-specific information e.g. 'id', 'single_end Groovy Map called 'meta'. This information may not be required in some instances, for example indexing reference genome files. Will the module require a meta map of sample information? (yes/no) [y/n] (y): y INFO Created / edited following files: - ./software/star/align/functions.nf ./software/star/align/main.nf ./software/star/align/meta.yml ./tests/software/star/align/main.nf ./tests/software/star/align/test.yml - ./tests/config/pytest_software.yml + ./tests/config/pytest_modules.yml ``` ### Create a module test config file From 500e7c4df13d9a9c67a1ec548bbf05bf4f2630d3 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Mon, 22 Nov 2021 22:18:36 +0100 Subject: [PATCH 151/266] Remove functions.nf from pipeline template local module (fix lint) --- .../modules/local/functions.nf | 78 ------------------- 1 file changed, 78 deletions(-) delete mode 100644 nf_core/pipeline-template/modules/local/functions.nf diff --git a/nf_core/pipeline-template/modules/local/functions.nf b/nf_core/pipeline-template/modules/local/functions.nf deleted file mode 100644 index 85628ee0eb..0000000000 --- a/nf_core/pipeline-template/modules/local/functions.nf +++ /dev/null @@ -1,78 +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() -} - -// -// Extract name of module from process name using $task.process -// -def getProcessName(task_process) { - return task_process.tokenize(':')[-1] -} - -// -// 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) { - def ioptions = initOptions(args.options) - def path_list = [ ioptions.publish_dir ?: args.publish_dir ] - - // Do not publish versions.yml unless running from pytest workflow - if (args.filename.equals('versions.yml') && !System.getenv("NF_CORE_MODULES_TEST")) { - return null - } - 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" - } -} From f5cf6b71d291439917e93f1b74d05879922e2ff2 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Mon, 22 Nov 2021 22:40:34 +0100 Subject: [PATCH 152/266] Join dir to readme to determine repository type --- nf_core/modules/module_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/modules/module_utils.py b/nf_core/modules/module_utils.py index 6ef675bf95..ce8a802746 100644 --- a/nf_core/modules/module_utils.py +++ b/nf_core/modules/module_utils.py @@ -344,8 +344,8 @@ def get_repo_type(dir): raise LookupError("Could not find directory: {}".format(dir)) # Determine repository type - if os.path.exists("README.md"): - with open("README.md") as fh: + if os.path.exists(os.path.join(dir, "README.md")): + with open(os.path.join(dir, "README.md")) as fh: if fh.readline().rstrip().startswith("# ![nf-core/modules]"): return "modules" else: From ba6c35685128f0c08040ddc47ba1ef048a59cf7d Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 23 Nov 2021 10:23:53 +0100 Subject: [PATCH 153/266] Remove functions_nf script --- nf_core/modules/lint/functions_nf.py | 56 ---------------------------- 1 file changed, 56 deletions(-) delete mode 100644 nf_core/modules/lint/functions_nf.py diff --git a/nf_core/modules/lint/functions_nf.py b/nf_core/modules/lint/functions_nf.py deleted file mode 100644 index 648b1c3173..0000000000 --- a/nf_core/modules/lint/functions_nf.py +++ /dev/null @@ -1,56 +0,0 @@ -# #!/usr/bin/env python -# import logging -# import os -# import nf_core - -# log = logging.getLogger(__name__) - - -# def functions_nf(module_lint_object, module): -# """ -# Lint a functions.nf file -# Verifies that the file exists and contains all necessary functions -# """ -# local_copy = None -# template_copy = None -# try: -# with open(module.function_nf, "r") as fh: -# lines = fh.readlines() -# module.passed.append(("functions_nf_exists", "'functions.nf' exists", module.function_nf)) -# except FileNotFoundError as e: -# module.failed.append(("functions_nf_exists", "'functions.nf' does not exist", module.function_nf)) -# return - -# # Test whether all required functions are present -# required_functions = ["getSoftwareName", "getProcessName", "initOptions", "getPathFromList", "saveFiles"] -# lines = "\n".join(lines) -# contains_all_functions = True -# for f in required_functions: -# if not "def " + f in lines: -# module.failed.append(("functions_nf_func_exist", "Function is missing: `{f}`", module.function_nf)) -# contains_all_functions = False -# if contains_all_functions: -# module.passed.append(("functions_nf_func_exist", "All functions present", module.function_nf)) - -# # Compare functions.nf file to the most recent template -# # Get file content of the module functions.nf -# try: -# local_copy = open(module.function_nf, "r").read() -# except FileNotFoundError as e: -# log.error(f"Could not open {module.function_nf}") - -# # Get the template file -# template_copy_path = os.path.join(os.path.dirname(nf_core.__file__), "module-template/modules/functions.nf") -# try: -# template_copy = open(template_copy_path, "r").read() -# except FileNotFoundError as e: -# log.error(f"Could not open {template_copy_path}") - -# # Compare the files -# if local_copy and template_copy: -# if local_copy != template_copy: -# module.failed.append( -# ("function_nf_comparison", "New version of functions.nf available", module.function_nf) -# ) -# else: -# module.passed.append(("function_nf_comparison", "functions.nf is up to date", module.function_nf)) From 0e7915b7ab036c6e0984a850fa0ce59e6e627fe7 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 23 Nov 2021 10:24:44 +0100 Subject: [PATCH 154/266] Remove checks for options defined in module main --- nf_core/modules/lint/main_nf.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index 8101472dfb..227b080dba 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -26,14 +26,6 @@ def main_nf(module_lint_object, module): module.failed.append(("main_nf_exists", "Module file does not exist", module.main_nf)) return - # Check that options are defined - # initoptions_re = re.compile(r"\s*options\s*=\s*initOptions\s*\(\s*params\.options\s*\)\s*") - # paramsoptions_re = re.compile(r"\s*params\.options\s*=\s*\[:\]\s*") - # if any(initoptions_re.match(l) for l in lines) and any(paramsoptions_re.match(l) for l in lines): - # module.passed.append(("main_nf_options", "'options' variable specified", module.main_nf)) - # else: - # module.warned.append(("main_nf_options", "'options' variable not specified", module.main_nf)) - # Go through module main.nf file and switch state according to current section # Perform section-specific linting state = "module" From 80ae5f04296bc3b0b753778f9412f2928a14c381 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 23 Nov 2021 10:25:18 +0100 Subject: [PATCH 155/266] Remove functions.nf from module_changes checks --- nf_core/modules/lint/module_changes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/modules/lint/module_changes.py b/nf_core/modules/lint/module_changes.py index 57f3b9a03f..773e5db408 100644 --- a/nf_core/modules/lint/module_changes.py +++ b/nf_core/modules/lint/module_changes.py @@ -11,12 +11,12 @@ def module_changes(module_lint_object, module): """ Checks whether installed nf-core modules have changed compared to the original repository - Downloads the 'main.nf', 'functions.nf' and 'meta.yml' files for every module + Downloads the 'main.nf' and 'meta.yml' files for every module and compares them to the local copies If the module has a 'git_sha', the file content is checked against this sha """ - files_to_check = ["main.nf", "functions.nf", "meta.yml"] + files_to_check = ["main.nf", "meta.yml"] # Loop over nf-core modules module_base_url = f"https://raw.githubusercontent.com/{module_lint_object.modules_repo.name}/{module_lint_object.modules_repo.branch}/modules/{module.module_name}/" From b50670443a85e9bfa702d8eaead71a353726f0a4 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 23 Nov 2021 11:19:47 +0100 Subject: [PATCH 156/266] Update pipeline template to DSL2 v2.0 --- nf_core/pipeline-template/conf/modules.config | 58 +++++++++++-------- .../modules/local/samplesheet_check.nf | 10 +--- .../pipeline-template/workflows/pipeline.nf | 12 ++-- 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/nf_core/pipeline-template/conf/modules.config b/nf_core/pipeline-template/conf/modules.config index 0b1bfdec20..207c641461 100644 --- a/nf_core/pipeline-template/conf/modules.config +++ b/nf_core/pipeline-template/conf/modules.config @@ -1,32 +1,44 @@ /* ======================================================================================== - 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.suffix = File name suffix for output files. ---------------------------------------------------------------------------------------- */ -params { - modules { - 'fastqc' { - args = "--quiet" - } - 'multiqc' { - args = "" - } +process { + + publishDir = [ + path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + + withName: FASTQC { + ext.args = '--quiet' + } + withName: CUSTOM_DUMPSOFTWAREVERSIONS { + publishDir = [ + path: { "${params.outdir}/pipeline_info" }, + mode: params.publish_dir_mode, + pattern: '*_versions.yml' + ] + } + withName: MULTIQC { + ext.args = '' + } + + // Local Subworkflows + // INPUT_CHECK + withName: '.*:INPUT_CHECK:SAMPLESHEET_CHECK' { + publishDir = [ + path: { "${params.outdir}/pipeline_info" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] } } diff --git a/nf_core/pipeline-template/modules/local/samplesheet_check.nf b/nf_core/pipeline-template/modules/local/samplesheet_check.nf index b8354f35e4..a8b5fb13c0 100644 --- a/nf_core/pipeline-template/modules/local/samplesheet_check.nf +++ b/nf_core/pipeline-template/modules/local/samplesheet_check.nf @@ -1,13 +1,5 @@ -// Import generic module functions -include { saveFiles; getProcessName } 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) { @@ -30,7 +22,7 @@ process SAMPLESHEET_CHECK { samplesheet.valid.csv cat <<-END_VERSIONS > versions.yml - ${getProcessName(task.process)}: + SAMPLESHEET_CHECK: python: \$(python --version | sed 's/Python //g') END_VERSIONS """ diff --git a/nf_core/pipeline-template/workflows/pipeline.nf b/nf_core/pipeline-template/workflows/pipeline.nf index 68ca75e1de..bd7dacd2b5 100644 --- a/nf_core/pipeline-template/workflows/pipeline.nf +++ b/nf_core/pipeline-template/workflows/pipeline.nf @@ -33,7 +33,7 @@ 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() +// def modules = params.modules.clone() // // SUBWORKFLOW: Consisting of a mix of local and nf-core/modules @@ -46,15 +46,15 @@ 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\""]) : '' +// 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 { CUSTOM_DUMPSOFTWAREVERSIONS } from '../modules/nf-core/modules/custom/dumpsoftwareversions/main' addParams( options: [publish_files : ['_versions.yml':'']] ) +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 { CUSTOM_DUMPSOFTWAREVERSIONS } from '../modules/nf-core/modules/custom/dumpsoftwareversions/main' // addParams( options: [publish_files : ['_versions.yml':'']] ) /* ======================================================================================== From 8bf5af620bbbf7d9b01f1b1fd63c0f9bb08df770 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 23 Nov 2021 13:15:23 +0100 Subject: [PATCH 157/266] Manually update modules in pipeline template --- .../custom/dumpsoftwareversions/functions.nf | 78 ------------------- .../custom/dumpsoftwareversions/main.nf | 25 +++--- .../nf-core/modules/fastqc/functions.nf | 78 ------------------- .../modules/nf-core/modules/fastqc/main.nf | 26 +++---- .../nf-core/modules/multiqc/functions.nf | 78 ------------------- .../modules/nf-core/modules/multiqc/main.nf | 17 ++-- 6 files changed, 29 insertions(+), 273 deletions(-) delete mode 100644 nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/functions.nf delete mode 100644 nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf delete mode 100644 nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf diff --git a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/functions.nf deleted file mode 100644 index 85628ee0eb..0000000000 --- a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/functions.nf +++ /dev/null @@ -1,78 +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() -} - -// -// Extract name of module from process name using $task.process -// -def getProcessName(task_process) { - return task_process.tokenize(':')[-1] -} - -// -// 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) { - def ioptions = initOptions(args.options) - def path_list = [ ioptions.publish_dir ?: args.publish_dir ] - - // Do not publish versions.yml unless running from pytest workflow - if (args.filename.equals('versions.yml') && !System.getenv("NF_CORE_MODULES_TEST")) { - return null - } - 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/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf index faf2073f75..3b7f7d8647 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf @@ -1,15 +1,6 @@ -// Import generic module functions -include { initOptions; saveFiles; getSoftwareName; getProcessName } from './functions' - -params.options = [:] -options = initOptions(params.options) - process CUSTOM_DUMPSOFTWAREVERSIONS { label 'process_low' - publishDir "${params.outdir}", - mode: params.publish_dir_mode, - saveAs: { filename -> saveFiles(filename:filename, options:params.options, publish_dir:'pipeline_info', meta:[:], publish_by_meta:[]) } - + // Requires `pyyaml` which does not have a dedicated container but is in the MultiQC container conda (params.enable_conda ? "bioconda::multiqc=1.11" : null) if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) { @@ -104,3 +95,17 @@ process CUSTOM_DUMPSOFTWAREVERSIONS { yaml.dump(module_versions, f, default_flow_style=False) """ } + +// +// Extract name of software tool from process name using $task.process +// +def getSoftwareName(task_process) { + return task_process.tokenize(':')[-1].tokenize('_')[0].toLowerCase() +} + +// +// Extract name of module from process name using $task.process +// +def getProcessName(task_process) { + return task_process.tokenize(':')[-1] +} \ No newline at end of file diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf deleted file mode 100644 index 85628ee0eb..0000000000 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/functions.nf +++ /dev/null @@ -1,78 +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() -} - -// -// Extract name of module from process name using $task.process -// -def getProcessName(task_process) { - return task_process.tokenize(':')[-1] -} - -// -// 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) { - def ioptions = initOptions(args.options) - def path_list = [ ioptions.publish_dir ?: args.publish_dir ] - - // Do not publish versions.yml unless running from pytest workflow - if (args.filename.equals('versions.yml') && !System.getenv("NF_CORE_MODULES_TEST")) { - return null - } - 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/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index 9f6cfc5538..ae44581bf4 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -1,16 +1,7 @@ -// Import generic module functions -include { initOptions; saveFiles; getSoftwareName; getProcessName } 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" @@ -28,26 +19,27 @@ process FASTQC { script: // Add soft-links to original FastQs for consistent naming in pipeline - def prefix = options.suffix ? "${meta.id}${options.suffix}" : "${meta.id}" + def prefix = task.ext.suffix ? "${meta.id}${task.ext.suffix}" : "${meta.id}" + def args = task.ext.args ?: '' if (meta.single_end) { """ [ ! -f ${prefix}.fastq.gz ] && ln -s $reads ${prefix}.fastq.gz - fastqc $options.args --threads $task.cpus ${prefix}.fastq.gz + fastqc $args --threads $task.cpus ${prefix}.fastq.gz cat <<-END_VERSIONS > versions.yml - ${getProcessName(task.process)}: - ${getSoftwareName(task.process)}: \$( fastqc --version | sed -e "s/FastQC v//g" ) + FASTQC: + 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 $args --threads $task.cpus ${prefix}_1.fastq.gz ${prefix}_2.fastq.gz cat <<-END_VERSIONS > versions.yml - ${getProcessName(task.process)}: - ${getSoftwareName(task.process)}: \$( fastqc --version | sed -e "s/FastQC v//g" ) + FASTQC: + fastqc: \$( fastqc --version | sed -e "s/FastQC v//g" ) END_VERSIONS """ } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf deleted file mode 100644 index 85628ee0eb..0000000000 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/functions.nf +++ /dev/null @@ -1,78 +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() -} - -// -// Extract name of module from process name using $task.process -// -def getProcessName(task_process) { - return task_process.tokenize(':')[-1] -} - -// -// 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) { - def ioptions = initOptions(args.options) - def path_list = [ ioptions.publish_dir ?: args.publish_dir ] - - // Do not publish versions.yml unless running from pytest workflow - if (args.filename.equals('versions.yml') && !System.getenv("NF_CORE_MODULES_TEST")) { - return null - } - 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/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index 0861aa5934..5033d0ca12 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -1,15 +1,7 @@ -// Import generic module functions -include { initOptions; saveFiles; getSoftwareName; getProcessName } 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.11' : null) if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) { container "https://depot.galaxyproject.org/singularity/multiqc:1.11--pyhdfd78af_0" @@ -27,12 +19,13 @@ process MULTIQC { path "versions.yml" , emit: versions script: + def args = task.ext.args ?: '' """ - multiqc -f $options.args . + multiqc -f $args . cat <<-END_VERSIONS > versions.yml - ${getProcessName(task.process)}: - ${getSoftwareName(task.process)}: \$( multiqc --version | sed -e "s/multiqc, version //g" ) + MULTIQC: + multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) END_VERSIONS """ } From 3846ebe8b4090d5c7580360103220e96d14ff250 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 23 Nov 2021 15:08:58 +0100 Subject: [PATCH 158/266] Use task.process within versions.yml creation --- nf_core/pipeline-template/modules/local/samplesheet_check.nf | 2 +- .../pipeline-template/modules/nf-core/modules/fastqc/main.nf | 2 +- .../pipeline-template/modules/nf-core/modules/multiqc/main.nf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nf_core/pipeline-template/modules/local/samplesheet_check.nf b/nf_core/pipeline-template/modules/local/samplesheet_check.nf index a8b5fb13c0..21dd208ade 100644 --- a/nf_core/pipeline-template/modules/local/samplesheet_check.nf +++ b/nf_core/pipeline-template/modules/local/samplesheet_check.nf @@ -22,7 +22,7 @@ process SAMPLESHEET_CHECK { samplesheet.valid.csv cat <<-END_VERSIONS > versions.yml - SAMPLESHEET_CHECK: + ${task.process}: python: \$(python --version | sed 's/Python //g') END_VERSIONS """ diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index ae44581bf4..ac96a8c2e6 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -38,7 +38,7 @@ process FASTQC { fastqc $args --threads $task.cpus ${prefix}_1.fastq.gz ${prefix}_2.fastq.gz cat <<-END_VERSIONS > versions.yml - FASTQC: + ${task.process}: fastqc: \$( fastqc --version | sed -e "s/FastQC v//g" ) END_VERSIONS """ diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index 5033d0ca12..6f22aa0920 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -24,7 +24,7 @@ process MULTIQC { multiqc -f $args . cat <<-END_VERSIONS > versions.yml - MULTIQC: + ${task.process}: multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) END_VERSIONS """ From 126f3da6a20995b13f76778070b13c4407b5bbf5 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 23 Nov 2021 23:06:16 +0100 Subject: [PATCH 159/266] Update how process and software name are retrieve for versions.yml --- CHANGELOG.md | 1 + nf_core/module-template/modules/main.nf | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06410ba0f8..88cb2574ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ * Update `nf-core modules create` help texts which were not changed with the introduction of the `--dir` flag * Check if README is from modules repo * Update module template to DSL2 v2.0 (remove `functions.nf` from modules template and updating `main.nf` ([#1289](https://github.com/nf-core/tools/pull/)) +* Substitute get process/module name custom functions in module `main.nf` using template replacement ([#1284](https://github.com/nf-core/tools/issues/1284)) ## [v2.1 - Zinc Zebra](https://github.com/nf-core/tools/releases/tag/2.1) - [2021-07-27] diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index ee28a4a117..530a2c6e90 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -71,8 +71,8 @@ process {{ tool_name_underscore|upper }} { $bam cat <<-END_VERSIONS > versions.yml - ${getProcessName(task.process)}: - ${getSoftwareName(task.process)}: \$( samtools --version 2>&1 | sed 's/^.*samtools //; s/Using.*\$//' ) + ${task.process}: + {{ tool }}: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//' )) END_VERSIONS """ } From d2004637051a8378a6aad2da4cb6d5752d6aa59c Mon Sep 17 00:00:00 2001 From: Jose Espinosa-Carrasco Date: Tue, 23 Nov 2021 23:12:18 +0100 Subject: [PATCH 160/266] Update nf_core/module-template/modules/main.nf Co-authored-by: Gregor Sturm --- nf_core/module-template/modules/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index ee28a4a117..c68d8c5273 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -6,7 +6,7 @@ // TODO nf-core: A module file SHOULD only define input and output files as command-line parameters. // All other parameters MUST be provided using "process.ext" directive, see here: // https://www.nextflow.io/docs/latest/process.html#ext -// where "process.ext" is a Groovy Map that MUST be provided via the modules.config file. +// where "process.ext" is a Groovy map. // Any parameters that need to be evaluated in the context of a particular sample // e.g. single-end/paired-end data MUST also be defined and evaluated appropriately. // TODO nf-core: Software that can be piped together SHOULD be added to separate module files From 84d1a31bbbaf815a43ea11b17466978d8dbc6215 Mon Sep 17 00:00:00 2001 From: Jose Espinosa-Carrasco Date: Tue, 23 Nov 2021 23:12:58 +0100 Subject: [PATCH 161/266] Update nf_core/modules/create.py Co-authored-by: Gregor Sturm --- nf_core/modules/create.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nf_core/modules/create.py b/nf_core/modules/create.py index 9c0a0bf7ba..c31c040c80 100644 --- a/nf_core/modules/create.py +++ b/nf_core/modules/create.py @@ -66,6 +66,7 @@ def create(self): modules/tests/modules/tool/subtool/ * main.nf * test.yml + * nextflow.config tests/config/pytest_modules.yml The function will attempt to automatically find a Bioconda package called From 070bbd79b3390ce21b8054cfe93355fc8412fc4e Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 23 Nov 2021 23:16:32 +0100 Subject: [PATCH 162/266] Error in functions.nf found in local or nf-core modules --- nf_core/modules/lint/__init__.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/nf_core/modules/lint/__init__.py b/nf_core/modules/lint/__init__.py index 50a6a2a573..a3ef862980 100644 --- a/nf_core/modules/lint/__init__.py +++ b/nf_core/modules/lint/__init__.py @@ -230,7 +230,14 @@ def get_installed_modules(self): # Filter local modules if os.path.exists(local_modules_dir): local_modules = os.listdir(local_modules_dir) - local_modules = sorted([x for x in local_modules if x.endswith(".nf")]) + for m in sorted([m for m in local_modules if m.endswith(".nf")]): + # Deprecation error if functions.nf is found + if m == "functions.nf": + raise ModuleLintException( + f"File '{m}' found in '{local_modules_dir}'has been deprecated since DSL2 v2.0!" + ) + else: + local_modules.append(m) # nf-core/modules if self.repo_type == "modules": @@ -248,6 +255,11 @@ def get_installed_modules(self): if not "main.nf" in m_content: for tool in m_content: nfcore_modules.append(os.path.join(m, tool)) + # Deprecation error if functions.nf is found + elif "functions.nf" in m_content: + raise ModuleLintException( + f"File 'functions.nf' found in '{m}' has been deprecated since DSL2 v2.0!" + ) else: nfcore_modules.append(m) From de79a0e2f036360f3e0a3158d0eb6315f3e0dda0 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 23 Nov 2021 23:21:21 +0100 Subject: [PATCH 163/266] Add errors if any of the deprecated functions after DSL2 v2.0 syntax adopted is found --- nf_core/modules/lint/main_nf.py | 36 +++++++++++---------------------- 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index 227b080dba..d05b7c2be8 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -26,6 +26,18 @@ def main_nf(module_lint_object, module): module.failed.append(("main_nf_exists", "Module file does not exist", module.main_nf)) return + deprecated_f = ["initOptions", "saveFiles", "getSoftwareName", "getProcessName"] + lines_j = "\n".join(lines) + for f in deprecated_f: + if f in lines_j: + module.failed.append( + ( + "deprecated_dsl2", + f"Function `{f}` has been deprecated since DSL2 v2.0", + module.main_nf, + ) + ) + # Go through module main.nf file and switch state according to current section # Perform section-specific linting state = "module" @@ -72,30 +84,6 @@ def main_nf(module_lint_object, module): else: module.failed.append(("main_nf_meta_output", "'meta' map not emitted in output channel(s)", module.main_nf)) - # if meta is specified, it should also be used as "saveAs ... meta:meta, publish_by_meta:['id']" - # save_as = [pl for pl in process_lines if "saveAs" in pl] - # if len(save_as) > 0 and re.search("\s*meta\s*:\s*meta", save_as[0]): - # module.passed.append(("main_nf_meta_saveas", "'meta:meta' specified in saveAs function", module.main_nf)) - # else: - # module.failed.append(("main_nf_meta_saveas", "'meta:meta' unspecified in saveAs function", module.main_nf)) - - # if len(save_as) > 0 and re.search("\s*publish_by_meta\s*:\s*\['id'\]", save_as[0]): - # module.passed.append( - # ( - # "main_nf_publish_meta_saveas", - # "'publish_by_meta:['id']' specified in saveAs function", - # module.main_nf, - # ) - # ) - # else: - # module.failed.append( - # ( - # "main_nf_publish_meta_saveas", - # "'publish_by_meta:['id']' unspecified in saveAs function", - # module.main_nf, - # ) - # ) - # Check that a software version is emitted if "version" in outputs: module.passed.append(("main_nf_version_emitted", "Module emits software version", module.main_nf)) From 027a53496b4e39b8122bd6604e9c283a34990645 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Wed, 24 Nov 2021 09:54:52 +0100 Subject: [PATCH 164/266] Add nextflow.config when a module is created --- nf_core/module-template/tests/nextflow.config | 7 +++++++ nf_core/module-template/tests/test.yml | 2 +- nf_core/modules/create.py | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 nf_core/module-template/tests/nextflow.config diff --git a/nf_core/module-template/tests/nextflow.config b/nf_core/module-template/tests/nextflow.config new file mode 100644 index 0000000000..495b860cc0 --- /dev/null +++ b/nf_core/module-template/tests/nextflow.config @@ -0,0 +1,7 @@ +process { + publishDir = [ + path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] +} \ No newline at end of file diff --git a/nf_core/module-template/tests/test.yml b/nf_core/module-template/tests/test.yml index 337444cb7b..7363cd231e 100644 --- a/nf_core/module-template/tests/test.yml +++ b/nf_core/module-template/tests/test.yml @@ -1,7 +1,7 @@ ## TODO nf-core: Please run the following command to build this file: # nf-core modules create-test-yml {{ tool }}{%- if subtool %}/{{ subtool }}{%- endif %} - name: {{ tool }}{{ ' '+subtool if subtool else '' }} - command: nextflow run ./tests/modules/{{ tool_dir }} -entry test_{{ tool_name_underscore }} -c tests/config/nextflow.config + command: nextflow run ./tests/modules/{{ tool_dir }} -entry test_{{ tool_name_underscore }} -c tests/config/nextflow.config -c tests/modules/{{ tool_dir }}/nextflow.config tags: - {{ tool }} {%- if subtool %} diff --git a/nf_core/modules/create.py b/nf_core/modules/create.py index c31c040c80..819f00eee9 100644 --- a/nf_core/modules/create.py +++ b/nf_core/modules/create.py @@ -359,5 +359,6 @@ def get_module_dirs(self): file_paths[os.path.join("modules", "meta.yml")] = os.path.join(software_dir, "meta.yml") file_paths[os.path.join("tests", "main.nf")] = os.path.join(test_dir, "main.nf") file_paths[os.path.join("tests", "test.yml")] = os.path.join(test_dir, "test.yml") + file_paths[os.path.join("tests", "nextflow.config")] = os.path.join(test_dir, "nextflow.config") return file_paths From 00371d1d2580ace3132df596bce066243efb4324 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Wed, 24 Nov 2021 09:55:42 +0100 Subject: [PATCH 165/266] Remove module parameters from pipeline template --- nf_core/pipeline-template/workflows/pipeline.nf | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/nf_core/pipeline-template/workflows/pipeline.nf b/nf_core/pipeline-template/workflows/pipeline.nf index bd7dacd2b5..9649e27ec8 100644 --- a/nf_core/pipeline-template/workflows/pipeline.nf +++ b/nf_core/pipeline-template/workflows/pipeline.nf @@ -32,9 +32,6 @@ 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() - // // SUBWORKFLOW: Consisting of a mix of local and nf-core/modules // @@ -46,15 +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 { CUSTOM_DUMPSOFTWAREVERSIONS } from '../modules/nf-core/modules/custom/dumpsoftwareversions/main' // addParams( options: [publish_files : ['_versions.yml':'']] ) +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' /* ======================================================================================== From 5bb4e83a1f08f0071c0a3780d51320ecee7e6129 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Wed, 24 Nov 2021 10:46:05 +0100 Subject: [PATCH 166/266] Update container directive with new syntax --- nf_core/module-template/modules/main.nf | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 6434ab43ac..f25fd62c5d 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -24,11 +24,10 @@ process {{ tool_name_underscore|upper }} { // For Conda, the build (i.e. "h9402c20_2") must be EXCLUDED to support installation on different operating systems. // TODO nf-core: See section in main README for further information regarding finding and adding container addresses to the section below. conda (params.enable_conda ? "{{ bioconda if bioconda else 'YOUR-TOOL-HERE' }}" : null) - if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) { - container "{{ singularity_container if singularity_container else 'https://depot.galaxyproject.org/singularity/YOUR-TOOL-HERE' }}" - } else { - container "{{ docker_container if docker_container else 'quay.io/biocontainers/YOUR-TOOL-HERE' }}" - } + container workflow.containerEngine == 'singularity' && + !task.ext.singularity_pull_docker_container ? + '{{ singularity_container if singularity_container else 'https://depot.galaxyproject.org/singularity/YOUR-TOOL-HERE' }}': + '{{ docker_container if docker_container else 'quay.io/biocontainers/YOUR-TOOL-HERE' }}' input: // TODO nf-core: Where applicable all sample-specific information e.g. "id", "single_end", "read_group" From 4c874ebf72300747a80bb7103069042b3a5dc944 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Wed, 24 Nov 2021 10:55:49 +0100 Subject: [PATCH 167/266] Adding publishDir to deprecated items in modules lint --- nf_core/modules/lint/main_nf.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index d05b7c2be8..204ab78518 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -26,14 +26,14 @@ def main_nf(module_lint_object, module): module.failed.append(("main_nf_exists", "Module file does not exist", module.main_nf)) return - deprecated_f = ["initOptions", "saveFiles", "getSoftwareName", "getProcessName"] + deprecated_i = ["initOptions", "saveFiles", "getSoftwareName", "getProcessName", "publishDir"] lines_j = "\n".join(lines) - for f in deprecated_f: - if f in lines_j: + for i in deprecated_i: + if i in lines_j: module.failed.append( ( "deprecated_dsl2", - f"Function `{f}` has been deprecated since DSL2 v2.0", + f"`{i}` has been deprecated since DSL2 v2.0", module.main_nf, ) ) From dbeee2b39b91cc1a1d4fd5d32c6131638ec02997 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Wed, 24 Nov 2021 17:03:15 +0100 Subject: [PATCH 168/266] Update nextflow.config in module test template --- nf_core/module-template/tests/nextflow.config | 8 +++----- nf_core/modules/lint/__init__.py | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/nf_core/module-template/tests/nextflow.config b/nf_core/module-template/tests/nextflow.config index 495b860cc0..50f50a7a35 100644 --- a/nf_core/module-template/tests/nextflow.config +++ b/nf_core/module-template/tests/nextflow.config @@ -1,7 +1,5 @@ process { - publishDir = [ - path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] + + publishDir = { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" } + } \ No newline at end of file diff --git a/nf_core/modules/lint/__init__.py b/nf_core/modules/lint/__init__.py index a3ef862980..69e093614b 100644 --- a/nf_core/modules/lint/__init__.py +++ b/nf_core/modules/lint/__init__.py @@ -234,7 +234,7 @@ def get_installed_modules(self): # Deprecation error if functions.nf is found if m == "functions.nf": raise ModuleLintException( - f"File '{m}' found in '{local_modules_dir}'has been deprecated since DSL2 v2.0!" + f"File '{m}' found in '{local_modules_dir}' has been deprecated since DSL2 v2.0!" ) else: local_modules.append(m) From 83dc6882f981f9188b63680aab4263884b7262ac Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Wed, 24 Nov 2021 17:38:00 +0100 Subject: [PATCH 169/266] Update test.yml for consistency --- nf_core/module-template/tests/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/module-template/tests/test.yml b/nf_core/module-template/tests/test.yml index 7363cd231e..8679a44943 100644 --- a/nf_core/module-template/tests/test.yml +++ b/nf_core/module-template/tests/test.yml @@ -1,7 +1,7 @@ ## TODO nf-core: Please run the following command to build this file: # nf-core modules create-test-yml {{ tool }}{%- if subtool %}/{{ subtool }}{%- endif %} - name: {{ tool }}{{ ' '+subtool if subtool else '' }} - command: nextflow run ./tests/modules/{{ tool_dir }} -entry test_{{ tool_name_underscore }} -c tests/config/nextflow.config -c tests/modules/{{ tool_dir }}/nextflow.config + command: nextflow run ./tests/modules/{{ tool_dir }} -entry test_{{ tool_name_underscore }} -c ./tests/config/nextflow.config -c ./tests/modules/{{ tool_dir }}/nextflow.config tags: - {{ tool }} {%- if subtool %} From 3360f38098e7bcd6bc1c18b33a3804e25ba443a2 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Wed, 24 Nov 2021 21:46:29 +0100 Subject: [PATCH 170/266] Quote the ${task.process} in versions.yml modules template --- nf_core/module-template/modules/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index f25fd62c5d..26a969cc1e 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -70,7 +70,7 @@ process {{ tool_name_underscore|upper }} { $bam cat <<-END_VERSIONS > versions.yml - ${task.process}: + "${task.process}": {{ tool }}: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//' )) END_VERSIONS """ From 17e78a0d0d87c555c0df2b4887cc1c526113b564 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Wed, 24 Nov 2021 22:33:45 +0100 Subject: [PATCH 171/266] Update container directive in main.nf module template --- nf_core/module-template/modules/main.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 26a969cc1e..8f24774d8a 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -24,10 +24,10 @@ process {{ tool_name_underscore|upper }} { // For Conda, the build (i.e. "h9402c20_2") must be EXCLUDED to support installation on different operating systems. // TODO nf-core: See section in main README for further information regarding finding and adding container addresses to the section below. conda (params.enable_conda ? "{{ bioconda if bioconda else 'YOUR-TOOL-HERE' }}" : null) - container workflow.containerEngine == 'singularity' && + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? '{{ singularity_container if singularity_container else 'https://depot.galaxyproject.org/singularity/YOUR-TOOL-HERE' }}': - '{{ docker_container if docker_container else 'quay.io/biocontainers/YOUR-TOOL-HERE' }}' + '{{ docker_container if docker_container else 'quay.io/biocontainers/YOUR-TOOL-HERE' }}' }" input: // TODO nf-core: Where applicable all sample-specific information e.g. "id", "single_end", "read_group" From bee3c37b3107b1eefc43c346686c41b981de6e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6rtenhuber?= Date: Thu, 25 Nov 2021 11:39:24 +0100 Subject: [PATCH 172/266] add missing space before `--fix`, update readme --- README.md | 46 +++++++++++++++++++++---------------------- nf_core/lint_utils.py | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index c874796049..616f806423 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,7 @@ $ nf-core list | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 ┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Pipeline Name ┃ Stars ┃ Latest Release ┃ Released ┃ Last Pulled ┃ Have latest release? ┃ @@ -219,7 +219,7 @@ $ nf-core list rna rna-seq | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 ┏━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Pipeline Name ┃ Stars ┃ Latest Release ┃ Released ┃ Last Pulled ┃ Have latest release? ┃ @@ -248,7 +248,7 @@ $ nf-core list -s stars | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 ┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Pipeline Name ┃ Stars ┃ Latest Release ┃ Released ┃ Last Pulled ┃ Have latest release? ┃ @@ -292,7 +292,7 @@ $ nf-core launch rnaseq | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 INFO This tool ignores any pipeline parameter defaults overwritten by Nextflow config files or profiles @@ -372,7 +372,7 @@ $ nf-core download | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 Specify the name of a nf-core pipeline or a GitHub repository name (user/repo). @@ -504,7 +504,7 @@ $ nf-core licences rnaseq | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 1.10 + nf-core/tools version 2.2 INFO Fetching licence information for 25 tools INFO Warning: This tool only prints licence information for the software tools packaged using conda. @@ -558,7 +558,7 @@ $ nf-core create | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 Workflow Name: nextbigthing Description: This pipeline analyses data from the next big 'omics technique @@ -603,7 +603,7 @@ $ nf-core lint |\ | |__ __ / ` / \ |__) |__ } { | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 INFO Testing pipeline: . ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ @@ -713,7 +713,7 @@ $ nf-core schema validate rnaseq nf-params.json | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 @@ -745,7 +745,7 @@ $ nf-core schema build nf-core-testpipeline | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 INFO [✓] Default parameters look valid INFO [✓] Pipeline schema looks valid (found 25 params) @@ -784,7 +784,7 @@ $ nf-core schema lint nextflow_schema.json | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 ERROR [✗] Pipeline schema does not follow nf-core specs: Definition subschema 'input_output_options' not included in schema 'allOf' @@ -800,14 +800,14 @@ Usage is `nf-core bump-version `, eg: ```console $ cd path/to/my_pipeline -$ nf-core bump-version . 1.7 +$ nf-core bump-version 1.7 ,--./,-. ___ __ __ __ ___ /,-._.--~\ |\ | |__ __ / ` / \ |__) |__ } { | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 @@ -862,7 +862,7 @@ $ nf-core sync my_pipeline/ | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 @@ -926,7 +926,7 @@ $ nf-core modules list remote | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 INFO Modules available from nf-core/modules (master) @@ -957,7 +957,7 @@ $ nf-core modules list local | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 INFO Modules installed in '.': @@ -983,7 +983,7 @@ $ nf-core modules install | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 ? Tool name: cat/fastq INFO Installing cat/fastq @@ -1010,7 +1010,7 @@ $ nf-core modules update | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 ? Tool name: fastqc INFO Updating 'nf-core/modules/fastqc' @@ -1072,7 +1072,7 @@ $ nf-core modules remove | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 ? Tool name: star/align INFO Removing star/align @@ -1103,7 +1103,7 @@ $ nf-core modules create | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 INFO Press enter to use default values (shown in brackets) or type your own responses. ctrl+click underlined text to open links. @@ -1141,7 +1141,7 @@ $ nf-core modules create-test-yml | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 INFO Press enter to use default values (shown in brackets) or type your own responses @@ -1187,7 +1187,7 @@ $ nf-core modules lint | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 ? Lint all modules or a single named module? Named module ? Tool name: star/align @@ -1226,7 +1226,7 @@ $ nf-core modules bump-versions -d modules | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/tools version 2.0 + nf-core/tools version 2.2 diff --git a/nf_core/lint_utils.py b/nf_core/lint_utils.py index ea62dd2b00..757a244ed9 100644 --- a/nf_core/lint_utils.py +++ b/nf_core/lint_utils.py @@ -38,7 +38,7 @@ def print_fixes(lint_obj, module_lint_obj): """Prints available and applied fixes""" if len(lint_obj.could_fix): - fix_cmd = "nf-core lint {}--fix {}".format( + fix_cmd = "nf-core lint {} --fix {}".format( "" if lint_obj.wf_path == "." else f"--dir {lint_obj.wf_path}", " --fix ".join(lint_obj.could_fix) ) console.print( From 8f4350afecb9a2ebe3008558df21922e101a64eb Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 14:57:09 +0000 Subject: [PATCH 173/266] Apply suggestions from code review --- nf_core/modules/lint/__init__.py | 4 ++-- nf_core/pipeline-template/conf/modules.config | 3 +++ nf_core/pipeline-template/modules/local/samplesheet_check.nf | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/nf_core/modules/lint/__init__.py b/nf_core/modules/lint/__init__.py index 69e093614b..cef7669f0f 100644 --- a/nf_core/modules/lint/__init__.py +++ b/nf_core/modules/lint/__init__.py @@ -234,7 +234,7 @@ def get_installed_modules(self): # Deprecation error if functions.nf is found if m == "functions.nf": raise ModuleLintException( - f"File '{m}' found in '{local_modules_dir}' has been deprecated since DSL2 v2.0!" + f"Deprecated file '{m}' found in '{local_modules_dir}' please delete it and update to latest syntax!" ) else: local_modules.append(m) @@ -258,7 +258,7 @@ def get_installed_modules(self): # Deprecation error if functions.nf is found elif "functions.nf" in m_content: raise ModuleLintException( - f"File 'functions.nf' found in '{m}' has been deprecated since DSL2 v2.0!" + f"Deprecated file '{m}' found in '{local_modules_dir}' please delete it and update to latest syntax!" ) else: nfcore_modules.append(m) diff --git a/nf_core/pipeline-template/conf/modules.config b/nf_core/pipeline-template/conf/modules.config index 207c641461..309c2abf58 100644 --- a/nf_core/pipeline-template/conf/modules.config +++ b/nf_core/pipeline-template/conf/modules.config @@ -21,6 +21,7 @@ process { withName: FASTQC { ext.args = '--quiet' } + withName: CUSTOM_DUMPSOFTWAREVERSIONS { publishDir = [ path: { "${params.outdir}/pipeline_info" }, @@ -28,6 +29,7 @@ process { pattern: '*_versions.yml' ] } + withName: MULTIQC { ext.args = '' } @@ -41,4 +43,5 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] } + } diff --git a/nf_core/pipeline-template/modules/local/samplesheet_check.nf b/nf_core/pipeline-template/modules/local/samplesheet_check.nf index 21dd208ade..504bf4bd9d 100644 --- a/nf_core/pipeline-template/modules/local/samplesheet_check.nf +++ b/nf_core/pipeline-template/modules/local/samplesheet_check.nf @@ -22,7 +22,7 @@ process SAMPLESHEET_CHECK { samplesheet.valid.csv cat <<-END_VERSIONS > versions.yml - ${task.process}: + "${task.process}": python: \$(python --version | sed 's/Python //g') END_VERSIONS """ From 9e884741ac53d4d4a620880640ed9b7206a7c039 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 15:25:24 +0000 Subject: [PATCH 174/266] Update module template --- nf_core/module-template/modules/main.nf | 10 +++++----- nf_core/module-template/tests/main.nf | 8 +++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 8f24774d8a..506a840134 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -2,11 +2,10 @@ // https://github.com/nf-core/modules/tree/master/modules // You can also ask for help via your pull request or on the #modules channel on the nf-core Slack workspace: // https://nf-co.re/join - // TODO nf-core: A module file SHOULD only define input and output files as command-line parameters. -// All other parameters MUST be provided using "process.ext" directive, see here: +// All other parameters MUST be provided using the "task.ext" directive, see here: // https://www.nextflow.io/docs/latest/process.html#ext -// where "process.ext" is a Groovy map. +// where "task.ext" is a string. // Any parameters that need to be evaluated in the context of a particular sample // e.g. single-end/paired-end data MUST also be defined and evaluated appropriately. // TODO nf-core: Software that can be piped together SHOULD be added to separate module files @@ -15,6 +14,7 @@ // bwa mem | samtools view -B -T ref.fasta // TODO nf-core: Optional inputs are not currently supported by Nextflow. However, using an empty // list (`[]`) instead of a file can be used to work around this issue. + process {{ tool_name_underscore|upper }} { tag {{ '"$meta.id"' if has_meta else "'$bam'" }} label '{{ process_label }}' @@ -48,12 +48,12 @@ process {{ tool_name_underscore|upper }} { {% if has_meta -%} def prefix = task.ext.suffix ? "${meta.id}${task.ext.suffix}" : "${meta.id}" {%- endif %} - def args = task.ext.args ?: '' + def args = task.ext.args ?: '' // TODO nf-core: Where possible, a command MUST be provided to obtain the version number of the software e.g. 1.10 // If the software is unable to output a version number on the command-line then it can be manually specified // e.g. https://github.com/nf-core/modules/blob/master/modules/homer/annotatepeaks/main.nf // Each software used MUST provide the software name and version number in the YAML version file (versions.yml) - // TODO nf-core: It MUST be possible to pass additional parameters to the tool as a command-line string via the "process.ext.args" directive + // TODO nf-core: It MUST be possible to pass additional parameters to the tool as a command-line string via the "task.ext.args" directive // TODO nf-core: If the tool supports multi-threading then you MUST provide the appropriate parameter // using the Nextflow "task" variable e.g. "--threads $task.cpus" // TODO nf-core: Please replace the example samtools command below with your module's command diff --git a/nf_core/module-template/tests/main.nf b/nf_core/module-template/tests/main.nf index 63bb1f7795..b172736c3a 100644 --- a/nf_core/module-template/tests/main.nf +++ b/nf_core/module-template/tests/main.nf @@ -2,12 +2,14 @@ nextflow.enable.dsl = 2 -include { {{ tool_name_underscore|upper }} } from '../../../{{ "../" if subtool else "" }}modules/{{ tool_dir }}/main.nf' addParams( options: [:] ) +include { {{ tool_name_underscore|upper }} } from '../../../{{ "../" if subtool else "" }}modules/{{ tool_dir }}/main.nf' workflow test_{{ tool_name_underscore }} { {% if has_meta %} - input = [ [ id:'test', single_end:false ], // meta map - file(params.test_data['sarscov2']['illumina']['test_paired_end_bam'], checkIfExists: true) ] + input = [ + [ id:'test', single_end:false ], // meta map + file(params.test_data['sarscov2']['illumina']['test_paired_end_bam'], checkIfExists: true) + ] {%- else %} input = file(params.test_data['sarscov2']['illumina']['test_single_end_bam'], checkIfExists: true) {%- endif %} From e7b21eae0333a696d5493371a992ae1ff2fa37a9 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 15:34:50 +0000 Subject: [PATCH 175/266] Update module main.nf --- nf_core/module-template/modules/main.nf | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 506a840134..0e4b549b25 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -24,8 +24,7 @@ process {{ tool_name_underscore|upper }} { // For Conda, the build (i.e. "h9402c20_2") must be EXCLUDED to support installation on different operating systems. // TODO nf-core: See section in main README for further information regarding finding and adding container addresses to the section below. conda (params.enable_conda ? "{{ bioconda if bioconda else 'YOUR-TOOL-HERE' }}" : null) - container "${ workflow.containerEngine == 'singularity' && - !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? '{{ singularity_container if singularity_container else 'https://depot.galaxyproject.org/singularity/YOUR-TOOL-HERE' }}': '{{ docker_container if docker_container else 'quay.io/biocontainers/YOUR-TOOL-HERE' }}' }" @@ -42,13 +41,13 @@ process {{ tool_name_underscore|upper }} { // TODO nf-core: Named file extensions MUST be emitted for ALL output channels {{ 'tuple val(meta), path("*.bam")' if has_meta else 'path "*.bam"' }}, emit: bam // TODO nf-core: List additional required output channels/values here - path "versions.yml" , emit: versions + path "versions.yml" , emit: versions script: + def args = task.ext.args ?: '' {% if has_meta -%} def prefix = task.ext.suffix ? "${meta.id}${task.ext.suffix}" : "${meta.id}" {%- endif %} - def args = task.ext.args ?: '' // TODO nf-core: Where possible, a command MUST be provided to obtain the version number of the software e.g. 1.10 // If the software is unable to output a version number on the command-line then it can be manually specified // e.g. https://github.com/nf-core/modules/blob/master/modules/homer/annotatepeaks/main.nf From f3c6041d8f2aff38e1389e163b4746106c8ec074 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 15:56:51 +0000 Subject: [PATCH 176/266] Bump NF version from 21.04.0 to 21.10.3 --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/workflows/create-lint-wf.yml | 2 +- .github/workflows/create-test-wf.yml | 2 +- CHANGELOG.md | 2 ++ nf_core/lint/readme.py | 4 ++-- .../.github/ISSUE_TEMPLATE/bug_report.yml | 2 +- nf_core/pipeline-template/.github/workflows/ci.yml | 2 +- nf_core/pipeline-template/README.md | 4 ++-- nf_core/pipeline-template/nextflow.config | 2 +- tests/test_bump_version.py | 8 ++++---- 10 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1046a3fa20..431cce198a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -34,7 +34,7 @@ body: attributes: label: System information description: | - * Nextflow version _(eg. 21.04.01)_ + * Nextflow version _(eg. 21.10.3)_ * Hardware _(eg. HPC, Desktop, Cloud)_ * Executor _(eg. slurm, local, awsbatch)_ * OS _(eg. CentOS Linux, macOS, Linux Mint)_ diff --git a/.github/workflows/create-lint-wf.yml b/.github/workflows/create-lint-wf.yml index dfc062eca7..d7cff18e29 100644 --- a/.github/workflows/create-lint-wf.yml +++ b/.github/workflows/create-lint-wf.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: # Nextflow versions: check pipeline minimum and latest edge version - nxf_ver: ["NXF_VER=21.04.0", "NXF_EDGE=1"] + nxf_ver: ["NXF_VER=21.10.3", "NXF_EDGE=1"] steps: - uses: actions/checkout@v2 name: Check out source-code repository diff --git a/.github/workflows/create-test-wf.yml b/.github/workflows/create-test-wf.yml index c2b9914f6e..a5e72f1ded 100644 --- a/.github/workflows/create-test-wf.yml +++ b/.github/workflows/create-test-wf.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: # Nextflow versions: check pipeline minimum and latest edge version - nxf_ver: ["NXF_VER=21.04.0", "NXF_EDGE=1"] + nxf_ver: ["NXF_VER=21.10.3", "NXF_EDGE=1"] steps: - uses: actions/checkout@v2 name: Check out source-code repository diff --git a/CHANGELOG.md b/CHANGELOG.md index 88cb2574ce..c0b41da2dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Template +* Bump minimun Nextflow version to 21.10.3 * Solve circular import when importing `nf_core.modules.lint` * Disable cache in `nf_core.utils.fetch_wf_config` while performing `test_wf_use_local_configs`. * Modify software version channel handling to support multiple software version emissions (e.g. from mulled containers), and multiple software versions. @@ -15,6 +16,7 @@ ### General +* Bump minimun Nextflow version to 21.10.3 * Changed `questionary` `ask()` to `unsafe_ask()` to not catch `KeyboardInterupts` ([#1237](https://github.com/nf-core/tools/issues/1237)) * Fixed bug in `nf-core launch` due to revisions specified with `-r` not being added to nextflow command. ([#1246](https://github.com/nf-core/tools/issues/1246)) * Update regex in `readme` test of `nf-core lint` to agree with the pipeline template ([#1260](https://github.com/nf-core/tools/issues/1260)) diff --git a/nf_core/lint/readme.py b/nf_core/lint/readme.py index ab240fc267..8df6155c5c 100644 --- a/nf_core/lint/readme.py +++ b/nf_core/lint/readme.py @@ -38,7 +38,7 @@ def readme(self): content = fh.read() # Check that there is a readme badge showing the minimum required version of Nextflow - # [![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/) # and that it has the correct version nf_badge_re = r"\[!\[Nextflow\]\(https://img\.shields\.io/badge/nextflow%20DSL2-%E2%89%A5([\d\.]+)-23aa62\.svg\?labelColor=000000\)\]\(https://www\.nextflow\.io/\)" match = re.search(nf_badge_re, content) @@ -62,7 +62,7 @@ def readme(self): warned.append("README did not have a Nextflow minimum version badge.") # Check that the minimum version mentioned in the quick start section is consistent - # Looking for: "1. Install [`Nextflow`](https://www.nextflow.io/docs/latest/getstarted.html#installation) (`>=21.04.0`)" + # Looking for: "1. Install [`Nextflow`](https://www.nextflow.io/docs/latest/getstarted.html#installation) (`>=21.10.3`)" nf_version_re = r"1\.\s*Install\s*\[`Nextflow`\]\(https://www.nextflow.io/docs/latest/getstarted.html#installation\)\s*\(`>=(\d*\.\d*\.\d*)`\)" match = re.search(nf_version_re, content) if match: diff --git a/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.yml b/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.yml index 9148922314..246362bf55 100644 --- a/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.yml @@ -44,7 +44,7 @@ body: attributes: label: System information description: | - * Nextflow version _(eg. 21.04.01)_ + * 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)_ diff --git a/nf_core/pipeline-template/.github/workflows/ci.yml b/nf_core/pipeline-template/.github/workflows/ci.yml index 458e98d758..22d6cf21d2 100644 --- a/nf_core/pipeline-template/.github/workflows/ci.yml +++ b/nf_core/pipeline-template/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: # Nextflow versions: check pipeline minimum and current latest - nxf_ver: ['21.04.0', ''] + nxf_ver: ['21.10.3', ''] steps: - name: Check out pipeline code uses: actions/checkout@v2 diff --git a/nf_core/pipeline-template/README.md b/nf_core/pipeline-template/README.md index 6003672ebc..e63c28f29b 100644 --- a/nf_core/pipeline-template/README.md +++ b/nf_core/pipeline-template/README.md @@ -5,7 +5,7 @@ [![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/{{ short_name }}/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,7 +33,7 @@ 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))_ diff --git a/nf_core/pipeline-template/nextflow.config b/nf_core/pipeline-template/nextflow.config index b6d7337714..51665264f8 100644 --- a/nf_core/pipeline-template/nextflow.config +++ b/nf_core/pipeline-template/nextflow.config @@ -159,7 +159,7 @@ manifest { homePage = 'https://github.com/{{ name }}' description = '{{ description }}' mainScript = 'main.nf' - nextflowVersion = '!>=21.04.0' + nextflowVersion = '!>=21.10.3' version = '{{ version }}' } diff --git a/tests/test_bump_version.py b/tests/test_bump_version.py index bd6c5a1a57..94d91e5bc2 100644 --- a/tests/test_bump_version.py +++ b/tests/test_bump_version.py @@ -61,24 +61,24 @@ def test_bump_nextflow_version(datafiles): pipeline_obj._load() # Bump the version number - nf_core.bump_version.bump_nextflow_version(pipeline_obj, "21.04.0") + nf_core.bump_version.bump_nextflow_version(pipeline_obj, "21.10.3") new_pipeline_obj = nf_core.utils.Pipeline(test_pipeline_dir) # Check nextflow.config new_pipeline_obj._load_pipeline_config() - assert new_pipeline_obj.nf_config["manifest.nextflowVersion"].strip("'\"") == "!>=21.04.0" + assert new_pipeline_obj.nf_config["manifest.nextflowVersion"].strip("'\"") == "!>=21.10.3" # Check .github/workflows/ci.yml with open(new_pipeline_obj._fp(".github/workflows/ci.yml")) as fh: ci_yaml = yaml.safe_load(fh) - assert ci_yaml["jobs"]["test"]["strategy"]["matrix"]["nxf_ver"][0] == "21.04.0" + assert ci_yaml["jobs"]["test"]["strategy"]["matrix"]["nxf_ver"][0] == "21.10.3" # Check README.md with open(new_pipeline_obj._fp("README.md")) as fh: readme = fh.read().splitlines() assert ( "[![Nextflow](https://img.shields.io/badge/nextflow%20DSL2-%E2%89%A5{}-23aa62.svg?labelColor=000000)](https://www.nextflow.io/)".format( - "21.04.0" + "21.10.3" ) in readme ) From f98c17b2c8fac960045825944dc27b0b9b481328 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Thu, 25 Nov 2021 17:02:24 +0100 Subject: [PATCH 177/266] Create README.md when a dummy module is created --- tests/test_modules.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_modules.py b/tests/test_modules.py index 2401dfa763..ef799e157b 100644 --- a/tests/test_modules.py +++ b/tests/test_modules.py @@ -19,6 +19,8 @@ def create_modules_repo_dummy(): os.makedirs(os.path.join(root_dir, "tests", "config")) with open(os.path.join(root_dir, "tests", "config", "pytest_modules.yml"), "w") as fh: fh.writelines(["test:", "\n - modules/test/**", "\n - tests/modules/test/**"]) + with open(os.path.join(root_dir, "README.md"), "w") as fh: + fh.writelines(["# ![nf-core/modules](docs/images/nfcore-modules_logo.png)", "\n"]) module_create = nf_core.modules.ModuleCreate(root_dir, "star/align", "@author", "process_medium", False, False) module_create.create() From 6708ec537da2b8134975d87912493334dcad53e4 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 16:04:48 +0000 Subject: [PATCH 178/266] Update pipeline template CI to use specified version and latest edge release --- nf_core/pipeline-template/.github/workflows/ci.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/nf_core/pipeline-template/.github/workflows/ci.yml b/nf_core/pipeline-template/.github/workflows/ci.yml index 22d6cf21d2..7595614d10 100644 --- a/nf_core/pipeline-template/.github/workflows/ci.yml +++ b/nf_core/pipeline-template/.github/workflows/ci.yml @@ -8,9 +8,6 @@ on: release: types: [published] -# Uncomment if we need an edge release of Nextflow again -# env: NXF_EDGE: 1 - jobs: test: name: Run workflow tests @@ -18,12 +15,11 @@ jobs: if: {% raw %}${{{% endraw %} github.event_name != 'push' || (github.event_name == 'push' && github.repository == '{{ name }}') {% raw %}}}{% endraw %} runs-on: ubuntu-latest env: - NXF_VER: {% raw %}${{ matrix.nxf_ver }}{% endraw %} NXF_ANSI_LOG: false strategy: matrix: - # Nextflow versions: check pipeline minimum and current latest - nxf_ver: ['21.10.3', ''] + # Nextflow versions: check pipeline minimum and latest edge version + nxf_ver: ["NXF_VER=21.10.3", "NXF_EDGE=1"] steps: - name: Check out pipeline code uses: actions/checkout@v2 @@ -34,6 +30,8 @@ jobs: run: | wget -qO- get.nextflow.io | bash sudo mv nextflow /usr/local/bin/ + export ${{ matrix.nxf_ver }} + nextflow self-update - name: Run pipeline with test data # TODO nf-core: You can customise CI pipeline run tests as required From 552f2bad8da5229059558bf47f6ab184bc8230e1 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 16:44:33 +0000 Subject: [PATCH 179/266] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0b41da2dd..efa8c1441d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Template * Bump minimun Nextflow version to 21.10.3 +* Convert pipeline template to updated Nextflow DSL2 syntax * Solve circular import when importing `nf_core.modules.lint` * Disable cache in `nf_core.utils.fetch_wf_config` while performing `test_wf_use_local_configs`. * Modify software version channel handling to support multiple software version emissions (e.g. from mulled containers), and multiple software versions. From 26362a801bc3fee8eb7d4e99f7c792e40ad9341f Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 16:44:46 +0000 Subject: [PATCH 180/266] Change syntax in modules.config --- nf_core/pipeline-template/conf/modules.config | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/nf_core/pipeline-template/conf/modules.config b/nf_core/pipeline-template/conf/modules.config index 309c2abf58..07c9720631 100644 --- a/nf_core/pipeline-template/conf/modules.config +++ b/nf_core/pipeline-template/conf/modules.config @@ -14,33 +14,27 @@ process { publishDir = [ path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, - mode: params.publish_dir_mode, + mode: 'copy', saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] - withName: FASTQC { - ext.args = '--quiet' - } - - withName: CUSTOM_DUMPSOFTWAREVERSIONS { + withName: SAMPLESHEET_CHECK' { publishDir = [ path: { "${params.outdir}/pipeline_info" }, - mode: params.publish_dir_mode, - pattern: '*_versions.yml' + mode: 'copy', + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] } - withName: MULTIQC { - ext.args = '' + withName: FASTQC { + ext.args = '--quiet' } - // Local Subworkflows - // INPUT_CHECK - withName: '.*:INPUT_CHECK:SAMPLESHEET_CHECK' { + withName: CUSTOM_DUMPSOFTWAREVERSIONS { publishDir = [ path: { "${params.outdir}/pipeline_info" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: 'copy', + pattern: '*_versions.yml' ] } From aa3dee85d6166594db8cfffcf1ea2131dbfab379 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 16:45:03 +0000 Subject: [PATCH 181/266] Remove docs relative to old DSL2 syntax --- nf_core/pipeline-template/docs/usage.md | 36 ------------------------- 1 file changed, 36 deletions(-) diff --git a/nf_core/pipeline-template/docs/usage.md b/nf_core/pipeline-template/docs/usage.md index b4a708d8ec..485af3af44 100644 --- a/nf_core/pipeline-template/docs/usage.md +++ b/nf_core/pipeline-template/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`. From 6b473289daa2dd2c1a496f538707f71cfbd292f7 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 16:46:13 +0000 Subject: [PATCH 182/266] Update nextflow.config for new syntax --- nf_core/pipeline-template/nextflow.config | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/nf_core/pipeline-template/nextflow.config b/nf_core/pipeline-template/nextflow.config index 51665264f8..912d90bb4c 100644 --- a/nf_core/pipeline-template/nextflow.config +++ b/nf_core/pipeline-template/nextflow.config @@ -34,9 +34,8 @@ 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' @@ -57,9 +56,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" @@ -163,6 +159,9 @@ manifest { version = '{{ version }}' } +// 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) { From 1490771a6120ef93fa6d94dd62400dcb187bbf05 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 16:46:31 +0000 Subject: [PATCH 183/266] Remve --singularity_pull_docker_container --- nf_core/pipeline-template/nextflow_schema.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/nf_core/pipeline-template/nextflow_schema.json b/nf_core/pipeline-template/nextflow_schema.json index c4b03824a8..fd95d67238 100644 --- a/nf_core/pipeline-template/nextflow_schema.json +++ b/nf_core/pipeline-template/nextflow_schema.json @@ -254,13 +254,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." } } } From 175ab50a60e43edf8c21e53c1efabc092c052c15 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 16:46:47 +0000 Subject: [PATCH 184/266] Remove addParams call! --- nf_core/pipeline-template/workflows/pipeline.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/workflows/pipeline.nf b/nf_core/pipeline-template/workflows/pipeline.nf index 9649e27ec8..7ac029844f 100644 --- a/nf_core/pipeline-template/workflows/pipeline.nf +++ b/nf_core/pipeline-template/workflows/pipeline.nf @@ -35,7 +35,7 @@ ch_multiqc_custom_config = params.multiqc_config ? Channel.fromPath(params.multi // // 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' /* ======================================================================================== From 19f49539ccb34bf42f3e330169442dc527d9afa7 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 16:46:54 +0000 Subject: [PATCH 185/266] Remove addParams call! --- nf_core/pipeline-template/subworkflows/local/input_check.nf | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nf_core/pipeline-template/subworkflows/local/input_check.nf b/nf_core/pipeline-template/subworkflows/local/input_check.nf index c1b071961c..cddcbb3ce0 100644 --- a/nf_core/pipeline-template/subworkflows/local/input_check.nf +++ b/nf_core/pipeline-template/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: From e4874b55c2ce3acbd326f99059d12b95aad822ef Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 16:47:13 +0000 Subject: [PATCH 186/266] Update container syntax --- .../pipeline-template/modules/local/samplesheet_check.nf | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/nf_core/pipeline-template/modules/local/samplesheet_check.nf b/nf_core/pipeline-template/modules/local/samplesheet_check.nf index 504bf4bd9d..ccfdfecc2c 100644 --- a/nf_core/pipeline-template/modules/local/samplesheet_check.nf +++ b/nf_core/pipeline-template/modules/local/samplesheet_check.nf @@ -2,11 +2,9 @@ process SAMPLESHEET_CHECK { tag "$samplesheet" 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 From dffa555f160109bcf4374606859bb177f6d67ee1 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 16:50:20 +0000 Subject: [PATCH 187/266] Remove --publish_dir_mode from pipeline template --- nf_core/pipeline-template/nextflow.config | 1 - nf_core/pipeline-template/nextflow_schema.json | 16 ---------------- 2 files changed, 17 deletions(-) diff --git a/nf_core/pipeline-template/nextflow.config b/nf_core/pipeline-template/nextflow.config index 912d90bb4c..dd8cb66102 100644 --- a/nf_core/pipeline-template/nextflow.config +++ b/nf_core/pipeline-template/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 diff --git a/nf_core/pipeline-template/nextflow_schema.json b/nf_core/pipeline-template/nextflow_schema.json index fd95d67238..9ccc78f362 100644 --- a/nf_core/pipeline-template/nextflow_schema.json +++ b/nf_core/pipeline-template/nextflow_schema.json @@ -178,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.", From 1c7727cc39095238a47b2c85fece8fd9ac22ecb3 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 17:06:29 +0000 Subject: [PATCH 188/266] Artificially update nf-core/modules to fix linting everywhere else --- .../custom/dumpsoftwareversions/main.nf | 102 ++---------------- .../custom/dumpsoftwareversions/meta.yml | 3 +- .../templates/dumpsoftwareversions.py | 89 +++++++++++++++ .../modules/nf-core/modules/fastqc/main.nf | 16 ++- .../modules/nf-core/modules/fastqc/meta.yml | 5 +- .../modules/nf-core/modules/multiqc/main.nf | 15 ++- .../modules/nf-core/modules/multiqc/meta.yml | 5 +- 7 files changed, 116 insertions(+), 119 deletions(-) create mode 100755 nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py diff --git a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf index 3b7f7d8647..934bb46723 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/main.nf @@ -1,13 +1,11 @@ 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) - if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) { - container "https://depot.galaxyproject.org/singularity/multiqc:1.11--pyhdfd78af_0" - } else { - container "quay.io/biocontainers/multiqc:1.11--pyhdfd78af_0" - } + 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 @@ -18,94 +16,6 @@ process CUSTOM_DUMPSOFTWAREVERSIONS { path "versions.yml" , emit: versions script: - """ - #!/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) - - module_versions = {} - module_versions["${getProcessName(task.process)}"] = { - 'python': platform.python_version(), - 'yaml': yaml.__version__ - } - - with open("$versions") as f: - workflow_versions = yaml.load(f, Loader=yaml.BaseLoader) | module_versions - - workflow_versions["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(workflow_versions) - } - - with open("software_versions.yml", 'w') as f: - yaml.dump(workflow_versions, 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(module_versions, f, default_flow_style=False) - """ + def args = task.ext.args ?: '' + template 'dumpsoftwareversions.py' } - -// -// Extract name of software tool from process name using $task.process -// -def getSoftwareName(task_process) { - return task_process.tokenize(':')[-1].tokenize('_')[0].toLowerCase() -} - -// -// Extract name of module from process name using $task.process -// -def getProcessName(task_process) { - return task_process.tokenize(':')[-1] -} \ No newline at end of file diff --git a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml index 8d4a6ed42c..5b5b8a6026 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml +++ b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/meta.yml @@ -8,7 +8,7 @@ tools: 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 @@ -31,3 +31,4 @@ output: authors: - "@drpatelh" + - "@grst" diff --git a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py new file mode 100755 index 0000000000..d139039254 --- /dev/null +++ b/nf_core/pipeline-template/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/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index ac96a8c2e6..673a00b841 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -1,13 +1,11 @@ process FASTQC { tag "$meta.id" label 'process_medium' - + 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) @@ -18,16 +16,16 @@ process FASTQC { path "versions.yml" , emit: versions script: + def args = task.ext.args ?: '' // Add soft-links to original FastQs for consistent naming in pipeline def prefix = task.ext.suffix ? "${meta.id}${task.ext.suffix}" : "${meta.id}" - def args = task.ext.args ?: '' if (meta.single_end) { """ [ ! -f ${prefix}.fastq.gz ] && ln -s $reads ${prefix}.fastq.gz fastqc $args --threads $task.cpus ${prefix}.fastq.gz cat <<-END_VERSIONS > versions.yml - FASTQC: + "${task.process}": fastqc: \$( fastqc --version | sed -e "s/FastQC v//g" ) END_VERSIONS """ @@ -38,7 +36,7 @@ process FASTQC { fastqc $args --threads $task.cpus ${prefix}_1.fastq.gz ${prefix}_2.fastq.gz cat <<-END_VERSIONS > versions.yml - ${task.process}: + "${task.process}": fastqc: \$( fastqc --version | sed -e "s/FastQC v//g" ) END_VERSIONS """ diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml index 48031356b5..b09553a3c3 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/meta.yml +++ b/nf_core/pipeline-template/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,9 +41,9 @@ output: type: file description: FastQC report archive pattern: "*_{fastqc.zip}" - - version: + - versions: type: file - description: File containing software version + description: File containing software versions pattern: "versions.yml" authors: - "@drpatelh" diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf index 6f22aa0920..3dceb162a3 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/main.nf @@ -1,13 +1,10 @@ - process MULTIQC { label 'process_medium' - + conda (params.enable_conda ? 'bioconda::multiqc=1.11' : null) - if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) { - container "https://depot.galaxyproject.org/singularity/multiqc:1.11--pyhdfd78af_0" - } else { - container "quay.io/biocontainers/multiqc:1.11--pyhdfd78af_0" - } + 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 @@ -19,12 +16,12 @@ process MULTIQC { path "versions.yml" , emit: versions script: - def args = task.ext.args ?: '' + def args = task.ext.args ?: '' """ multiqc -f $args . cat <<-END_VERSIONS > versions.yml - ${task.process}: + "${task.process}": multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) END_VERSIONS """ diff --git a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml b/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml index 2d99ec0d12..63c75a450a 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/multiqc/meta.yml +++ b/nf_core/pipeline-template/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,9 +30,9 @@ output: type: file description: Plots created by MultiQC pattern: "*_data" - - version: + - versions: type: file - description: File containing software version + description: File containing software versions pattern: "versions.yml" authors: - "@abhi18av" From bc1e63edf37615dbf3d6dd95d611e6d867d2ab11 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 25 Nov 2021 17:06:42 +0000 Subject: [PATCH 189/266] Update modules.json --- nf_core/pipeline-template/modules.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nf_core/pipeline-template/modules.json b/nf_core/pipeline-template/modules.json index db6aceae73..2cb00e761a 100644 --- a/nf_core/pipeline-template/modules.json +++ b/nf_core/pipeline-template/modules.json @@ -4,13 +4,13 @@ "repos": { "nf-core/modules": { "custom/dumpsoftwareversions": { - "git_sha": "84f2302920078b0cf7716b2a2e5fcc0be5c4531d" + "git_sha": "3aacd46da2b221ed47aaa05c413a828538d2c2ae" }, "fastqc": { - "git_sha": "7b3315591a149609e27914965f858c9a7e071564" + "git_sha": "3aacd46da2b221ed47aaa05c413a828538d2c2ae" }, "multiqc": { - "git_sha": "7b3315591a149609e27914965f858c9a7e071564" + "git_sha": "3aacd46da2b221ed47aaa05c413a828538d2c2ae" } } } From 66fc9e733523def307828e386f5f98ebdf76ee01 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Fri, 26 Nov 2021 10:29:57 +0100 Subject: [PATCH 190/266] Fix modules.config --- nf_core/pipeline-template/conf/modules.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/conf/modules.config b/nf_core/pipeline-template/conf/modules.config index 07c9720631..478869953b 100644 --- a/nf_core/pipeline-template/conf/modules.config +++ b/nf_core/pipeline-template/conf/modules.config @@ -18,7 +18,7 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] - withName: SAMPLESHEET_CHECK' { + withName: SAMPLESHEET_CHECK { publishDir = [ path: { "${params.outdir}/pipeline_info" }, mode: 'copy', From ae1f2a672bc1c2c6b7e4edc44eb58aaa522684ee Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Fri, 26 Nov 2021 11:08:57 +0100 Subject: [PATCH 191/266] Fix ci.yml template --- nf_core/pipeline-template/.github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/.github/workflows/ci.yml b/nf_core/pipeline-template/.github/workflows/ci.yml index 7595614d10..6a2646972b 100644 --- a/nf_core/pipeline-template/.github/workflows/ci.yml +++ b/nf_core/pipeline-template/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: run: | wget -qO- get.nextflow.io | bash sudo mv nextflow /usr/local/bin/ - export ${{ matrix.nxf_ver }} + export ${% raw %}{{ matrix.nxf_ver }}{% endraw %} nextflow self-update - name: Run pipeline with test data From 8c030f6256947294f03d0512e44caa7bb6d155ca Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Fri, 26 Nov 2021 10:35:34 +0000 Subject: [PATCH 192/266] Update modules.config --- nf_core/pipeline-template/conf/modules.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/conf/modules.config b/nf_core/pipeline-template/conf/modules.config index 07c9720631..478869953b 100644 --- a/nf_core/pipeline-template/conf/modules.config +++ b/nf_core/pipeline-template/conf/modules.config @@ -18,7 +18,7 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] - withName: SAMPLESHEET_CHECK' { + withName: SAMPLESHEET_CHECK { publishDir = [ path: { "${params.outdir}/pipeline_info" }, mode: 'copy', From 156c86c1a0296ee61d2f0c4208b4a9841557e553 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 26 Nov 2021 11:37:42 +0100 Subject: [PATCH 193/266] Update pytest options to only look search tests directory This makes pytest work in vscode even if you setup tests using the root directory of the repo. --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 266acbdcb6..223c17afb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,3 +7,4 @@ markers = [ "datafiles: load datafiles" ] testpaths = ["tests"] +norecursedirs = [ '.*', 'build', 'dist', '*.egg', 'data', '__pycache__', '.github', 'nf_core', 'docs'] \ No newline at end of file From 2aca38b93cf314ca555ce8053f28eedf7883d9db Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Fri, 26 Nov 2021 11:47:44 +0100 Subject: [PATCH 194/266] Fix nextflow version in ci.yml template --- nf_core/pipeline-template/.github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/.github/workflows/ci.yml b/nf_core/pipeline-template/.github/workflows/ci.yml index 6a2646972b..2b38ccf951 100644 --- a/nf_core/pipeline-template/.github/workflows/ci.yml +++ b/nf_core/pipeline-template/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: # Nextflow versions: check pipeline minimum and latest edge version - nxf_ver: ["NXF_VER=21.10.3", "NXF_EDGE=1"] + nxf_ver: ['21.10.3', 'NXF_EDGE=1'] steps: - name: Check out pipeline code uses: actions/checkout@v2 From 24cfa0f56bf3e7f8b42ad5b5d7804a241a52a1f9 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 26 Nov 2021 12:04:56 +0100 Subject: [PATCH 195/266] Fix the nasty nasty bug --- nf_core/modules/module_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/modules/module_utils.py b/nf_core/modules/module_utils.py index b5969c29e0..b3e47b812c 100644 --- a/nf_core/modules/module_utils.py +++ b/nf_core/modules/module_utils.py @@ -57,7 +57,7 @@ def get_module_git_log(module_name, modules_repo=None, per_page=30, page_nbr=1, if modules_repo is None: modules_repo = ModulesRepo() api_url = f"https://api.github.com/repos/{modules_repo.name}/commits" - api_url += f"?sha{modules_repo.branch}" + api_url += f"?sha={modules_repo.branch}" if module_name is not None: api_url += f"&path=modules/{module_name}" api_url += f"&page={page_nbr}" From 286abaf8247573d42ae8d9846106694f4fc059d4 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 26 Nov 2021 12:09:55 +0100 Subject: [PATCH 196/266] Fix modules test not discovering right repo type --- nf_core/modules/create.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nf_core/modules/create.py b/nf_core/modules/create.py index 819f00eee9..a9c071c4ce 100644 --- a/nf_core/modules/create.py +++ b/nf_core/modules/create.py @@ -281,9 +281,10 @@ def get_repo_type(self, directory): if dir is None or not os.path.exists(directory): raise UserWarning(f"Could not find directory: {directory}") + readme = os.path.join(directory, "README.md") # Determine repository type - if os.path.exists("README.md"): - with open("README.md") as fh: + if os.path.exists(readme): + with open(readme) as fh: if fh.readline().rstrip().startswith("# ![nf-core/modules]"): return "modules" else: From a7be87dea17b2b7eccae1099d88e7c08b627cb43 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 26 Nov 2021 12:12:26 +0100 Subject: [PATCH 197/266] Fix process name not used by new modules --- nf_core/modules/lint/main_nf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index 204ab78518..5676430c39 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -26,7 +26,7 @@ def main_nf(module_lint_object, module): module.failed.append(("main_nf_exists", "Module file does not exist", module.main_nf)) return - deprecated_i = ["initOptions", "saveFiles", "getSoftwareName", "getProcessName", "publishDir"] + deprecated_i = ["initOptions", "saveFiles", "getSoftwareName", "getProcessName", "publishDir"] lines_j = "\n".join(lines) for i in deprecated_i: if i in lines_j: @@ -101,7 +101,7 @@ def check_script_section(self, lines): script = "".join(lines) # check that process name is used for `versions.yml` - if re.search("\$\{\s*getProcessName\s*\(\s*task\.process\s*\)\s*\}", script): + if re.search("\$\{\s*task\.process\s*\}", script): self.passed.append(("main_nf_version_script", "Process name used for versions.yml", self.main_nf)) else: self.failed.append(("main_nf_version_script", "Process name not used for versions.yml", self.main_nf)) From a8842fb46a497d4aea5cfb1f770d02c861de4db9 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 26 Nov 2021 12:14:02 +0100 Subject: [PATCH 198/266] Fix black --- nf_core/modules/lint/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/modules/lint/__init__.py b/nf_core/modules/lint/__init__.py index cef7669f0f..fdef39252c 100644 --- a/nf_core/modules/lint/__init__.py +++ b/nf_core/modules/lint/__init__.py @@ -235,7 +235,7 @@ def get_installed_modules(self): if m == "functions.nf": raise ModuleLintException( f"Deprecated file '{m}' found in '{local_modules_dir}' please delete it and update to latest syntax!" - ) + ) else: local_modules.append(m) From 1d60935ba4156b7652422d906badfd08fc20c2ba Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 26 Nov 2021 12:22:06 +0100 Subject: [PATCH 199/266] Temporarily disable NXF_EDGE tests for CreateAndTestPipeline --- .github/workflows/create-test-wf.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/create-test-wf.yml b/.github/workflows/create-test-wf.yml index a5e72f1ded..4aa24e0894 100644 --- a/.github/workflows/create-test-wf.yml +++ b/.github/workflows/create-test-wf.yml @@ -9,7 +9,9 @@ jobs: strategy: matrix: # Nextflow versions: check pipeline minimum and latest edge version - nxf_ver: ["NXF_VER=21.10.3", "NXF_EDGE=1"] + nxf_ver: + - "NXF_VER=21.10.3" + # - "NXF_EDGE=1" steps: - uses: actions/checkout@v2 name: Check out source-code repository From c8ee21a43558ff32096799923fef152e534a073e Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 26 Nov 2021 13:05:03 +0100 Subject: [PATCH 200/266] Temporarily enable NXF_EDGE also in the pipeline template --- nf_core/pipeline-template/.github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/.github/workflows/ci.yml b/nf_core/pipeline-template/.github/workflows/ci.yml index 2b38ccf951..30ffef79ab 100644 --- a/nf_core/pipeline-template/.github/workflows/ci.yml +++ b/nf_core/pipeline-template/.github/workflows/ci.yml @@ -19,7 +19,9 @@ jobs: strategy: matrix: # Nextflow versions: check pipeline minimum and latest edge version - nxf_ver: ['21.10.3', 'NXF_EDGE=1'] + nxf_ver: + - 'NXF_EDGE=21.10.3' + # - 'NXF_EDGE=1' steps: - name: Check out pipeline code uses: actions/checkout@v2 From 9f11c855528dfba774d9d168140bde041c5168d4 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 26 Nov 2021 14:23:00 +0100 Subject: [PATCH 201/266] Check for functions.nf --- nf_core/modules/lint/__init__.py | 33 ++++++++----------- nf_core/modules/lint/module_deprecations.py | 20 +++++++++++ .../.github/workflows/ci.yml | 2 +- tests/modules/lint.py | 6 ++-- tests/test_bump_version.py | 2 +- 5 files changed, 38 insertions(+), 25 deletions(-) create mode 100644 nf_core/modules/lint/module_deprecations.py diff --git a/nf_core/modules/lint/__init__.py b/nf_core/modules/lint/__init__.py index fdef39252c..aae62e86a2 100644 --- a/nf_core/modules/lint/__init__.py +++ b/nf_core/modules/lint/__init__.py @@ -65,6 +65,7 @@ class ModuleLint(ModuleCommand): from .module_changes import module_changes from .module_tests import module_tests from .module_todos import module_todos + from .module_deprecations import module_deprecations from .module_version import module_version def __init__(self, dir): @@ -95,7 +96,7 @@ def __init__(self, dir): @staticmethod def _get_all_lint_tests(): - return ["main_nf", "meta_yml", "module_changes", "module_todos"] + return ["main_nf", "meta_yml", "module_changes", "module_todos", "module_deprecations"] def lint(self, module=None, key=(), all_modules=False, print_results=True, show_passed=False, local=False): """ @@ -229,15 +230,7 @@ def get_installed_modules(self): # Filter local modules if os.path.exists(local_modules_dir): - local_modules = os.listdir(local_modules_dir) - for m in sorted([m for m in local_modules if m.endswith(".nf")]): - # Deprecation error if functions.nf is found - if m == "functions.nf": - raise ModuleLintException( - f"Deprecated file '{m}' found in '{local_modules_dir}' please delete it and update to latest syntax!" - ) - else: - local_modules.append(m) + local_modules = sorted([x for x in local_modules if x.endswith(".nf")]) # nf-core/modules if self.repo_type == "modules": @@ -245,21 +238,21 @@ def get_installed_modules(self): # Get nf-core modules if os.path.exists(nfcore_modules_dir): - for m in sorted([m for m in os.listdir(nfcore_modules_dir) if not m == "lib"]): + for m in sorted(os.listdir(nfcore_modules_dir)): if not os.path.isdir(os.path.join(nfcore_modules_dir, m)): raise ModuleLintException( f"File found in '{nfcore_modules_dir}': '{m}'! This directory should only contain module directories." ) - m_content = os.listdir(os.path.join(nfcore_modules_dir, m)) + + module_dir = os.path.join(nfcore_modules_dir, m) + module_subdir = os.listdir(module_dir) # Not a module, but contains sub-modules - if not "main.nf" in m_content: - for tool in m_content: - nfcore_modules.append(os.path.join(m, tool)) - # Deprecation error if functions.nf is found - elif "functions.nf" in m_content: - raise ModuleLintException( - f"Deprecated file '{m}' found in '{local_modules_dir}' please delete it and update to latest syntax!" - ) + if "main.nf" not in module_subdir: + for path in module_subdir: + module_subdir_path = os.path.join(nfcore_modules_dir, m, path) + if os.path.isdir(module_subdir_path): + if os.path.exists(os.path.join(module_subdir_path, "main.nf")): + nfcore_modules.append(os.path.join(m, path)) else: nfcore_modules.append(m) diff --git a/nf_core/modules/lint/module_deprecations.py b/nf_core/modules/lint/module_deprecations.py new file mode 100644 index 0000000000..4c7904985b --- /dev/null +++ b/nf_core/modules/lint/module_deprecations.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +import logging +import os + +log = logging.getLogger(__name__) + + +def module_deprecations(module_lint_object, module): + """ + Check that the modules are up to the latest nf-core standard + """ + module.wf_path = module.module_dir + if "functions.nf" in os.listdir(module.module_dir): + module.failed.append( + ( + "module_deprecations", + f"Deprecated file 'functions.nf' found please delete it and update to latest syntax!", + module.module_dir, + ) + ) diff --git a/nf_core/pipeline-template/.github/workflows/ci.yml b/nf_core/pipeline-template/.github/workflows/ci.yml index 30ffef79ab..0d477a075b 100644 --- a/nf_core/pipeline-template/.github/workflows/ci.yml +++ b/nf_core/pipeline-template/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: matrix: # Nextflow versions: check pipeline minimum and latest edge version nxf_ver: - - 'NXF_EDGE=21.10.3' + - 'NXF_VER=21.10.3' # - 'NXF_EDGE=1' steps: - name: Check out pipeline code diff --git a/tests/modules/lint.py b/tests/modules/lint.py index 0d25f62880..0f60377d5e 100644 --- a/tests/modules/lint.py +++ b/tests/modules/lint.py @@ -6,9 +6,9 @@ def test_modules_lint_trimgalore(self): self.mods_install.install("trimgalore") module_lint = nf_core.modules.ModuleLint(dir=self.pipeline_dir) module_lint.lint(print_results=False, module="trimgalore") + assert len(module_lint.failed) == 0, f"Linting failed with {[x.__dict__ for x in module_lint.failed]}" assert len(module_lint.passed) > 0 assert len(module_lint.warned) >= 0 - assert len(module_lint.failed) == 0, f"Linting failed with {[x.__dict__ for x in module_lint.failed]}" def test_modules_lint_empty(self): @@ -18,15 +18,15 @@ def test_modules_lint_empty(self): self.mods_remove.remove("custom/dumpsoftwareversions") module_lint = nf_core.modules.ModuleLint(dir=self.pipeline_dir) module_lint.lint(print_results=False, all_modules=True) + assert len(module_lint.failed) == 0, f"Linting failed with {[x.__dict__ for x in module_lint.failed]}" assert len(module_lint.passed) == 0 assert len(module_lint.warned) == 0 - assert len(module_lint.failed) == 0, f"Linting failed with {[x.__dict__ for x in module_lint.failed]}" def test_modules_lint_new_modules(self): """lint all modules in nf-core/modules repo clone""" module_lint = nf_core.modules.ModuleLint(dir=self.nfcore_modules) module_lint.lint(print_results=True, all_modules=True) + assert len(module_lint.failed) == 0, f"Linting failed with {[x.__dict__ for x in module_lint.failed]}" assert len(module_lint.passed) > 0 assert len(module_lint.warned) >= 0 - assert len(module_lint.failed) == 0, f"Linting failed with {[x.__dict__ for x in module_lint.failed]}" diff --git a/tests/test_bump_version.py b/tests/test_bump_version.py index 94d91e5bc2..39840fe6d1 100644 --- a/tests/test_bump_version.py +++ b/tests/test_bump_version.py @@ -71,7 +71,7 @@ def test_bump_nextflow_version(datafiles): # Check .github/workflows/ci.yml with open(new_pipeline_obj._fp(".github/workflows/ci.yml")) as fh: ci_yaml = yaml.safe_load(fh) - assert ci_yaml["jobs"]["test"]["strategy"]["matrix"]["nxf_ver"][0] == "21.10.3" + assert ci_yaml["jobs"]["test"]["strategy"]["matrix"]["nxf_ver"][0] == "NXF_VER=21.10.3" # Check README.md with open(new_pipeline_obj._fp("README.md")) as fh: From c3c8f8711de699e16c603619cc22e0cda760b9a1 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 26 Nov 2021 14:31:43 +0100 Subject: [PATCH 202/266] Make missing task.process in script section a warning It was a failure before, but there are some legitimate cases to not have it, e.g. when using the `template` feature. --- nf_core/modules/lint/main_nf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index 5676430c39..dcee7d380b 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -104,7 +104,7 @@ def check_script_section(self, lines): if re.search("\$\{\s*task\.process\s*\}", script): self.passed.append(("main_nf_version_script", "Process name used for versions.yml", self.main_nf)) else: - self.failed.append(("main_nf_version_script", "Process name not used for versions.yml", self.main_nf)) + self.warned.append(("main_nf_version_script", "Process name not used for versions.yml", self.main_nf)) # check for prefix (only if module has a meta map as input) if self.has_meta: From c83156f5a6cb8b82f0bbe12501612ec097b5b9ac Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 26 Nov 2021 14:36:33 +0100 Subject: [PATCH 203/266] Fix lint for nextflow version in CI script --- nf_core/lint/actions_ci.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/lint/actions_ci.py b/nf_core/lint/actions_ci.py index fd138ed0ab..48056a9422 100644 --- a/nf_core/lint/actions_ci.py +++ b/nf_core/lint/actions_ci.py @@ -131,7 +131,7 @@ def actions_ci(self): # Check that we are testing the minimum nextflow version try: matrix = ciwf["jobs"]["test"]["strategy"]["matrix"]["nxf_ver"] - assert any([self.minNextflowVersion in matrix]) + assert any([f"NXF_VER={self.minNextflowVersion}" in matrix]) except (KeyError, TypeError): failed.append("'.github/workflows/ci.yml' does not check minimum NF version") except AssertionError: From b882bee215c3b53734e368e857da180a0d70018e Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 26 Nov 2021 14:40:07 +0100 Subject: [PATCH 204/266] against dev branch From 1bab2c40e7abfb8cc67e8122545606b72922f7d4 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Fri, 26 Nov 2021 14:31:33 +0000 Subject: [PATCH 205/266] Re-install nf-core/modules in pipeline template --- nf_core/pipeline-template/modules.json | 6 +++--- .../dumpsoftwareversions/templates/dumpsoftwareversions.py | 0 2 files changed, 3 insertions(+), 3 deletions(-) mode change 100755 => 100644 nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py diff --git a/nf_core/pipeline-template/modules.json b/nf_core/pipeline-template/modules.json index 2cb00e761a..e02a95aba8 100644 --- a/nf_core/pipeline-template/modules.json +++ b/nf_core/pipeline-template/modules.json @@ -4,13 +4,13 @@ "repos": { "nf-core/modules": { "custom/dumpsoftwareversions": { - "git_sha": "3aacd46da2b221ed47aaa05c413a828538d2c2ae" + "git_sha": "20d8250d9f39ddb05dfb437603aaf99b5c0b2b41" }, "fastqc": { - "git_sha": "3aacd46da2b221ed47aaa05c413a828538d2c2ae" + "git_sha": "20d8250d9f39ddb05dfb437603aaf99b5c0b2b41" }, "multiqc": { - "git_sha": "3aacd46da2b221ed47aaa05c413a828538d2c2ae" + "git_sha": "20d8250d9f39ddb05dfb437603aaf99b5c0b2b41" } } } diff --git a/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py b/nf_core/pipeline-template/modules/nf-core/modules/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py old mode 100755 new mode 100644 From 2d33ffbf85296dc724985025d7d82eae75a2af54 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Fri, 26 Nov 2021 15:00:09 +0000 Subject: [PATCH 206/266] Amend error messages for old DSL2 syntax --- nf_core/modules/lint/main_nf.py | 2 +- nf_core/modules/lint/module_deprecations.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index c772e37102..d8d7516c9a 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -33,7 +33,7 @@ def main_nf(module_lint_object, module): module.failed.append( ( "deprecated_dsl2", - f"`{i}` has been deprecated since DSL2 v2.0", + f"`{i}` specified. No longer required for the latest nf-core/modules syntax!", module.main_nf, ) ) diff --git a/nf_core/modules/lint/module_deprecations.py b/nf_core/modules/lint/module_deprecations.py index 4c7904985b..e697342950 100644 --- a/nf_core/modules/lint/module_deprecations.py +++ b/nf_core/modules/lint/module_deprecations.py @@ -14,7 +14,7 @@ def module_deprecations(module_lint_object, module): module.failed.append( ( "module_deprecations", - f"Deprecated file 'functions.nf' found please delete it and update to latest syntax!", + f"Deprecated file 'functions.nf' found. No longer required for the latest nf-core/modules syntax!", module.module_dir, ) ) From d52a0aca051f01f28a9f24db17da26a31808b28b Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Fri, 26 Nov 2021 15:08:55 +0000 Subject: [PATCH 207/266] Highlight functions.nf --- nf_core/modules/lint/module_deprecations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/modules/lint/module_deprecations.py b/nf_core/modules/lint/module_deprecations.py index e697342950..0a2990d9d0 100644 --- a/nf_core/modules/lint/module_deprecations.py +++ b/nf_core/modules/lint/module_deprecations.py @@ -14,7 +14,7 @@ def module_deprecations(module_lint_object, module): module.failed.append( ( "module_deprecations", - f"Deprecated file 'functions.nf' found. No longer required for the latest nf-core/modules syntax!", + f"Deprecated file `functions.nf` found. No longer required for the latest nf-core/modules syntax!", module.module_dir, ) ) From 0dc877603f40d874d96da61c3e04f4e7d0a7e52b Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Fri, 26 Nov 2021 17:29:24 +0100 Subject: [PATCH 208/266] describe how to access private modules repos --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index e440979bde..9c26b483ab 100644 --- a/README.md +++ b/README.md @@ -902,6 +902,8 @@ This allows multiple pipelines to use the same code for share tools and gives a The nf-core DSL2 modules repository is at +### Custom remote modules + The modules supercommand comes with two flags for specifying a custom remote: * `--github-repository `: Specify the repository from which the modules should be fetched. Defaults to `nf-core/modules`. @@ -909,6 +911,34 @@ The modules supercommand comes with two flags for specifying a custom remote: Note that a custom remote must follow a similar directory structure to that of `nf-core/moduleś` for the `nf-core modules` commands to work properly. +### Private remote modules + +In order to get access to your private modules repo, you need to create +the `~/.config/gh/hosts.yml` file, which is the same file required by +[GitHub CLI](https://cli.github.com/) to deal with private repositories. +Such file is structured as follow: + +```conf +github.com: + oauth_token: + user: + git_protocol: +``` + +The easiest way to create this configuration file is through *GitHub CLI*: follow +its [installation instructions](https://cli.github.com/manual/installation) +and then call: + +```bash +gh auth login +``` + +After that, you will be able to list and install your private modules without +providing your github credentials through command line, by using `--github-repository` +and `--branch` options properly. +See the documentation on [gh auth login](https://cli.github.com/manual/gh_auth_login>) +to get more information. + ### List modules The `nf-core modules list` command provides the subcommands `remote` and `local` for listing modules installed in a remote repository and in the local pipeline respectively. Both subcommands come with the `--key ` option for filtering the modules by keywords. From f5b41c28395beca1a0ce550c47f4df48d78c2e65 Mon Sep 17 00:00:00 2001 From: Paolo Cozzi Date: Fri, 26 Nov 2021 17:36:14 +0100 Subject: [PATCH 209/266] solve issue with markdownlint --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9c26b483ab..385e8f9820 100644 --- a/README.md +++ b/README.md @@ -933,9 +933,9 @@ and then call: gh auth login ``` -After that, you will be able to list and install your private modules without +After that, you will be able to list and install your private modules without providing your github credentials through command line, by using `--github-repository` -and `--branch` options properly. +and `--branch` options properly. See the documentation on [gh auth login](https://cli.github.com/manual/gh_auth_login>) to get more information. From 7baee696d103ba5c1249856ebea9a32c3f82381e Mon Sep 17 00:00:00 2001 From: ggabernet Date: Fri, 26 Nov 2021 20:03:14 +0100 Subject: [PATCH 210/266] fix linting comment --- CHANGELOG.md | 1 + nf_core/pipeline-template/.github/workflows/linting_comment.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe02faadcf..89372f0495 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * Update `.gitattributes` to mark installed modules and subworkflows as `linguist-generated` ([#1311](https://github.com/nf-core/tools/issues/1311)) * New YAML issue templates for pipeline bug reports and feature requests, with a much richer interface ([#1165](https://github.com/nf-core/tools/pull/1165)) * Update AWS test GitHub Actions to use v2 of [nf-core/tower-action](https://github.com/nf-core/tower-action) +* Post linting comment even when `linting.yml` fails ### General diff --git a/nf_core/pipeline-template/.github/workflows/linting_comment.yml b/nf_core/pipeline-template/.github/workflows/linting_comment.yml index 0c718c0d9b..68a0feee18 100644 --- a/nf_core/pipeline-template/.github/workflows/linting_comment.yml +++ b/nf_core/pipeline-template/.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 From deb5bb20218f584f237ae0413ea807e338bf0dac Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 26 Nov 2021 20:03:36 +0100 Subject: [PATCH 211/266] Nicer syntax for ci.yml matrix env variables Trying to make it a bit easier to read / understand --- .../pipeline-template/.github/workflows/ci.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/nf_core/pipeline-template/.github/workflows/ci.yml b/nf_core/pipeline-template/.github/workflows/ci.yml index 0d477a075b..b9e59ec692 100644 --- a/nf_core/pipeline-template/.github/workflows/ci.yml +++ b/nf_core/pipeline-template/.github/workflows/ci.yml @@ -18,10 +18,14 @@ jobs: NXF_ANSI_LOG: false strategy: matrix: - # Nextflow versions: check pipeline minimum and latest edge version - nxf_ver: - - 'NXF_VER=21.10.3' - # - 'NXF_EDGE=1' + # 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 @@ -29,10 +33,13 @@ jobs: - name: Install Nextflow env: CAPSULE_LOG: none + {% raw %-} + NXF_VER: ${{ matrix.NXF_VER }} + NXF_EDGE: ${{ matrix.NXF_EDGE }} + {-% endraw %} run: | wget -qO- get.nextflow.io | bash sudo mv nextflow /usr/local/bin/ - export ${% raw %}{{ matrix.nxf_ver }}{% endraw %} nextflow self-update - name: Run pipeline with test data From a75ca80ad60f2b69416c21ffccd4ccde104aad55 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 26 Nov 2021 20:08:13 +0100 Subject: [PATCH 212/266] Hyphens in the wrong place --- nf_core/pipeline-template/.github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/pipeline-template/.github/workflows/ci.yml b/nf_core/pipeline-template/.github/workflows/ci.yml index b9e59ec692..a7189aac69 100644 --- a/nf_core/pipeline-template/.github/workflows/ci.yml +++ b/nf_core/pipeline-template/.github/workflows/ci.yml @@ -33,10 +33,10 @@ jobs: - name: Install Nextflow env: CAPSULE_LOG: none - {% raw %-} + {% raw -%} NXF_VER: ${{ matrix.NXF_VER }} NXF_EDGE: ${{ matrix.NXF_EDGE }} - {-% endraw %} + {%- endraw %} run: | wget -qO- get.nextflow.io | bash sudo mv nextflow /usr/local/bin/ From 179ed262e255a2c9a7229262a3acecf9bf9ded4c Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 26 Nov 2021 20:20:52 +0100 Subject: [PATCH 213/266] Comment out NXF_EDGE --- nf_core/pipeline-template/.github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/.github/workflows/ci.yml b/nf_core/pipeline-template/.github/workflows/ci.yml index a7189aac69..0e8ec45ae2 100644 --- a/nf_core/pipeline-template/.github/workflows/ci.yml +++ b/nf_core/pipeline-template/.github/workflows/ci.yml @@ -35,7 +35,9 @@ jobs: CAPSULE_LOG: none {% raw -%} NXF_VER: ${{ matrix.NXF_VER }} - NXF_EDGE: ${{ matrix.NXF_EDGE }} + # 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 }} {%- endraw %} run: | wget -qO- get.nextflow.io | bash From 2d9a75c3e39f976f34622e9496c2bc6b6cf23455 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Mon, 29 Nov 2021 09:36:10 +0100 Subject: [PATCH 214/266] Update nextflow.config Update with comment --- nf_core/pipeline-template/nextflow.config | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nf_core/pipeline-template/nextflow.config b/nf_core/pipeline-template/nextflow.config index 19e023192d..cb29bfa2de 100644 --- a/nf_core/pipeline-template/nextflow.config +++ b/nf_core/pipeline-template/nextflow.config @@ -126,6 +126,9 @@ profiles { } // 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" From 551a5f09e1857d9fe913e0b98dc2743378e5a9f9 Mon Sep 17 00:00:00 2001 From: James Fellows Yates Date: Tue, 30 Nov 2021 10:46:01 +0100 Subject: [PATCH 215/266] Replaces suffix with completely customisable prefix variable --- nf_core/module-template/modules/main.nf | 2 +- nf_core/modules/lint/main_nf.py | 2 +- nf_core/pipeline-template/conf/modules.config | 2 +- .../pipeline-template/modules/nf-core/modules/fastqc/main.nf | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nf_core/module-template/modules/main.nf b/nf_core/module-template/modules/main.nf index 0e4b549b25..d152e970b3 100644 --- a/nf_core/module-template/modules/main.nf +++ b/nf_core/module-template/modules/main.nf @@ -46,7 +46,7 @@ process {{ tool_name_underscore|upper }} { script: def args = task.ext.args ?: '' {% if has_meta -%} - def prefix = task.ext.suffix ? "${meta.id}${task.ext.suffix}" : "${meta.id}" + def prefix = task.ext.prefix ?: "${meta.id}" {%- endif %} // TODO nf-core: Where possible, a command MUST be provided to obtain the version number of the software e.g. 1.10 // If the software is unable to output a version number on the command-line then it can be manually specified diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index d8d7516c9a..032463f6bc 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -108,7 +108,7 @@ def check_script_section(self, lines): # check for prefix (only if module has a meta map as input) if self.has_meta: - if re.search("\s*prefix\s*=\s*options.suffix", script): + if re.search("\s*prefix\s*=\s*task.args.prefix", script): self.passed.append(("main_nf_meta_prefix", "'prefix' specified in script section", self.main_nf)) else: self.failed.append(("main_nf_meta_prefix", "'prefix' unspecified in script section", self.main_nf)) diff --git a/nf_core/pipeline-template/conf/modules.config b/nf_core/pipeline-template/conf/modules.config index 478869953b..a0506a4db4 100644 --- a/nf_core/pipeline-template/conf/modules.config +++ b/nf_core/pipeline-template/conf/modules.config @@ -6,7 +6,7 @@ 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.suffix = File name suffix for output files. + ext.prefix = File name prefix for output files. ---------------------------------------------------------------------------------------- */ diff --git a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf index 673a00b841..d250eca075 100644 --- a/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf +++ b/nf_core/pipeline-template/modules/nf-core/modules/fastqc/main.nf @@ -18,7 +18,7 @@ process FASTQC { script: def args = task.ext.args ?: '' // Add soft-links to original FastQs for consistent naming in pipeline - def prefix = task.ext.suffix ? "${meta.id}${task.ext.suffix}" : "${meta.id}" + def prefix = task.ext.prefix ?: "${meta.id}" if (meta.single_end) { """ [ ! -f ${prefix}.fastq.gz ] && ln -s $reads ${prefix}.fastq.gz From 26cb85773081df2722126ae08fab2569cae6dc80 Mon Sep 17 00:00:00 2001 From: "James A. Fellows Yates" Date: Tue, 30 Nov 2021 11:24:04 +0100 Subject: [PATCH 216/266] Update nf_core/modules/lint/main_nf.py --- nf_core/modules/lint/main_nf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/modules/lint/main_nf.py b/nf_core/modules/lint/main_nf.py index 032463f6bc..065910d3b8 100644 --- a/nf_core/modules/lint/main_nf.py +++ b/nf_core/modules/lint/main_nf.py @@ -108,7 +108,7 @@ def check_script_section(self, lines): # check for prefix (only if module has a meta map as input) if self.has_meta: - if re.search("\s*prefix\s*=\s*task.args.prefix", script): + if re.search("\s*prefix\s*=\s*task.ext.prefix", script): self.passed.append(("main_nf_meta_prefix", "'prefix' specified in script section", self.main_nf)) else: self.failed.append(("main_nf_meta_prefix", "'prefix' unspecified in script section", self.main_nf)) From 5cd3b385acc757d56ccd11b7793388c8eb5c3479 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Wed, 1 Dec 2021 15:46:37 +0100 Subject: [PATCH 217/266] Update contributing in pipeline template --- .../pipeline-template/.github/CONTRIBUTING.md | 38 ++++--------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/nf_core/pipeline-template/.github/CONTRIBUTING.md b/nf_core/pipeline-template/.github/CONTRIBUTING.md index bf43ef3fd4..edc89666ab 100644 --- a/nf_core/pipeline-template/.github/CONTRIBUTING.md +++ b/nf_core/pipeline-template/.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). From 73052b8e0a79ae3a3261d6f1a49c545263d73548 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Thu, 2 Dec 2021 11:48:21 +0100 Subject: [PATCH 218/266] Add NXF_VER to aws test yml files --- nf_core/pipeline-template/.github/workflows/awsfulltest.yml | 2 +- nf_core/pipeline-template/.github/workflows/awstest.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/pipeline-template/.github/workflows/awsfulltest.yml b/nf_core/pipeline-template/.github/workflows/awsfulltest.yml index 2b83987597..8e0ab65b23 100644 --- a/nf_core/pipeline-template/.github/workflows/awsfulltest.yml +++ b/nf_core/pipeline-template/.github/workflows/awsfulltest.yml @@ -31,4 +31,4 @@ jobs: "outdir": "s3://{% raw %}${{ secrets.AWS_S3_BUCKET }}{% endraw %}/{{ short_name }}/{% raw %}results-${{ github.sha }}{% endraw %}" } profiles: test_full,aws_tower - + pre_run_script: 'export NXF_VER=21.10.3' diff --git a/nf_core/pipeline-template/.github/workflows/awstest.yml b/nf_core/pipeline-template/.github/workflows/awstest.yml index 3ef7d2b93d..ffa04f14d4 100644 --- a/nf_core/pipeline-template/.github/workflows/awstest.yml +++ b/nf_core/pipeline-template/.github/workflows/awstest.yml @@ -25,4 +25,4 @@ jobs: "outdir": "s3://{% raw %}${{ secrets.AWS_S3_BUCKET }}{% endraw %}/{{ short_name }}/{% raw %}results-${{ github.sha }}{% endraw %}" } profiles: test,aws_tower - + pre_run_script: 'export NXF_VER=21.10.3' From 78174c01b1c44a1bc8f2df9b01b7ad0fc4e74790 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Thu, 2 Dec 2021 11:59:21 +0100 Subject: [PATCH 219/266] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 202e87d7cb..ae5038965c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ * New YAML issue templates for pipeline bug reports and feature requests, with a much richer interface ([#1165](https://github.com/nf-core/tools/pull/1165)) * Update AWS test GitHub Actions to use v2 of [nf-core/tower-action](https://github.com/nf-core/tower-action) * Post linting comment even when `linting.yml` fails +* Update `CONTRIBUTION.md` bullets to remove points related to `scrape_software_versions.py` +* Update AWS test to set Nextflow version to 21.10.3 ### General From a505a2b536b124987221abf64b577e1c9f25910f Mon Sep 17 00:00:00 2001 From: James Fellows Yates Date: Thu, 2 Dec 2021 13:49:10 +0100 Subject: [PATCH 220/266] update modules in pipeline template to new prefix syntax --- nf_core/pipeline-template/modules.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/modules.json b/nf_core/pipeline-template/modules.json index e02a95aba8..853ad6a1e3 100644 --- a/nf_core/pipeline-template/modules.json +++ b/nf_core/pipeline-template/modules.json @@ -7,7 +7,7 @@ "git_sha": "20d8250d9f39ddb05dfb437603aaf99b5c0b2b41" }, "fastqc": { - "git_sha": "20d8250d9f39ddb05dfb437603aaf99b5c0b2b41" + "git_sha": "9d0cad583b9a71a6509b754fdf589cbfbed08961" }, "multiqc": { "git_sha": "20d8250d9f39ddb05dfb437603aaf99b5c0b2b41" From d53f8e092e3c4fa8ad6e3ed8ff1d12f6f60e8073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6rtenhuber?= Date: Thu, 2 Dec 2021 17:08:54 +0100 Subject: [PATCH 221/266] update repo logos for light and dark theme --- README.md | 3 ++- docs/images/nfcore-tools_logo.png | Bin 31762 -> 0 bytes docs/images/nfcore-tools_logo_dark.png | Bin 0 -> 64937 bytes docs/images/nfcore-tools_logo_light.png | Bin 0 -> 64856 bytes nf_core/create.py | 19 +++++++++--------- nf_core/lint/files_exist.py | 12 +++++++---- nf_core/lint/files_unchanged.py | 12 +++++++---- nf_core/pipeline-template/README.md | 3 ++- .../assets/sendmail_template.txt | 4 ++-- 9 files changed, 31 insertions(+), 22 deletions(-) delete mode 100644 docs/images/nfcore-tools_logo.png create mode 100644 docs/images/nfcore-tools_logo_dark.png create mode 100644 docs/images/nfcore-tools_logo_light.png diff --git a/README.md b/README.md index 616f806423..9981755fc5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# ![nf-core/tools](docs/images/nfcore-tools_logo.png) +# ![nf-core/tools](docs/images/nfcore-tools_logo_light.png#gh-light-mode-only) +# ![nf-core/tools](docs/images/nfcore-tools_logo_dark.png#gh-dark-mode-only) [![Python tests](https://github.com/nf-core/tools/workflows/Python%20tests/badge.svg?branch=master&event=push)](https://github.com/nf-core/tools/actions?query=workflow%3A%22Python+tests%22+branch%3Amaster) [![codecov](https://codecov.io/gh/nf-core/tools/branch/master/graph/badge.svg)](https://codecov.io/gh/nf-core/tools) diff --git a/docs/images/nfcore-tools_logo.png b/docs/images/nfcore-tools_logo.png deleted file mode 100644 index e45813591e90a97ac9db506cfc2d854814e3726c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31762 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 MUQMo0=CR-Z0jKc0jsO4v diff --git a/docs/images/nfcore-tools_logo_dark.png b/docs/images/nfcore-tools_logo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..1b9cc02b17522232a22912d2e069f87db0ed823e GIT binary patch literal 64937 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%0D6Aha9ZZvy3Owj0R@{kHIA1_W(q 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 literal 0 HcmV?d00001 diff --git a/docs/images/nfcore-tools_logo_light.png b/docs/images/nfcore-tools_logo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..cc4ccea1cb12e60311b513bc7de0c339ebd591cb GIT binary patch literal 64856 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�ev`Q}H7y#R1zKkC5n?0_OP7^FqWWeXhX0t0pNK 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

)bNtXB<>| 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 literal 0 HcmV?d00001 diff --git a/nf_core/create.py b/nf_core/create.py index de30f8d3dd..763673a312 100644 --- a/nf_core/create.py +++ b/nf_core/create.py @@ -145,23 +145,22 @@ def render_template(self): def make_pipeline_logo(self): """Fetch a logo for the new pipeline from the nf-core website""" - logo_url = f"https://nf-co.re/logo/{self.short_name}" + logo_url = f"https://nf-co.re/logo/{self.short_name}?theme=light" log.debug(f"Fetching logo from {logo_url}") - email_logo_path = f"{self.outdir}/assets/{self.name_noslash}_logo.png" + email_logo_path = f"{self.outdir}/assets/{self.name_noslash}_logo_light.png" os.makedirs(os.path.dirname(email_logo_path), exist_ok=True) log.debug(f"Writing logo to '{email_logo_path}'") r = requests.get(f"{logo_url}?w=400") with open(email_logo_path, "wb") as fh: fh.write(r.content) - - readme_logo_path = f"{self.outdir}/docs/images/{self.name_noslash}_logo.png" - - log.debug(f"Writing logo to '{readme_logo_path}'") - os.makedirs(os.path.dirname(readme_logo_path), exist_ok=True) - r = requests.get(f"{logo_url}?w=600") - with open(readme_logo_path, "wb") as fh: - fh.write(r.content) + for theme in ["dark", "light"]: + readme_logo_path = f"{self.outdir}/docs/images/{self.name_noslash}_logo_{theme}.png" + log.debug(f"Writing logo to '{readme_logo_path}'") + os.makedirs(os.path.dirname(readme_logo_path), exist_ok=True) + r = requests.get(f"{logo_url}?w=600&theme={theme}") + with open(readme_logo_path, "wb") as fh: + fh.write(r.content) def git_init_pipeline(self): """Initialises the new pipeline as a Git repository and submits first commit.""" diff --git a/nf_core/lint/files_exist.py b/nf_core/lint/files_exist.py index cb7d5f978c..8800157a7f 100644 --- a/nf_core/lint/files_exist.py +++ b/nf_core/lint/files_exist.py @@ -34,7 +34,8 @@ def files_exist(self): [LICENSE, LICENSE.md, LICENCE, LICENCE.md] # NB: British / American spelling assets/email_template.html assets/email_template.txt - assets/nf-core-PIPELINE_logo.png + assets/nf-core-PIPELINE_logo_light.png + assets/nf-core-PIPELINE_logo_dark.png assets/sendmail_template.txt conf/modules.config conf/test.config @@ -42,7 +43,8 @@ def files_exist(self): CHANGELOG.md CITATIONS.md CODE_OF_CONDUCT.md - docs/images/nf-core-PIPELINE_logo.png + docs/images/nf-core-PIPELINE_logo_light.png + docs/images/nf-core-PIPELINE_logo_dark.png docs/output.md docs/README.md docs/usage.md @@ -119,11 +121,13 @@ def files_exist(self): [os.path.join("assets", "email_template.html")], [os.path.join("assets", "email_template.txt")], [os.path.join("assets", "sendmail_template.txt")], - [os.path.join("assets", f"nf-core-{short_name}_logo.png")], + [os.path.join("assets", f"nf-core-{short_name}_logo_light.png")], + [os.path.join("assets", f"nf-core-{short_name}_logo_dark.png")], [os.path.join("conf", "modules.config")], [os.path.join("conf", "test.config")], [os.path.join("conf", "test_full.config")], - [os.path.join("docs", "images", f"nf-core-{short_name}_logo.png")], + [os.path.join("docs", "images", f"nf-core-{short_name}_logo_light.png")], + [os.path.join("docs", "images", f"nf-core-{short_name}_logo_dark.png")], [os.path.join("docs", "output.md")], [os.path.join("docs", "README.md")], [os.path.join("docs", "README.md")], diff --git a/nf_core/lint/files_unchanged.py b/nf_core/lint/files_unchanged.py index 39842ae32c..806e201f46 100644 --- a/nf_core/lint/files_unchanged.py +++ b/nf_core/lint/files_unchanged.py @@ -31,10 +31,12 @@ def files_unchanged(self): .github/workflows/linting.yml assets/email_template.html assets/email_template.txt - assets/nf-core-PIPELINE_logo.png + assets/nf-core-PIPELINE_logo_light.png + assets/nf-core-PIPELINE_logo_dark.png assets/sendmail_template.txt CODE_OF_CONDUCT.md - docs/images/nf-core-PIPELINE_logo.png + docs/images/nf-core-PIPELINE_logo_light.png + docs/images/nf-core-PIPELINE_logo_dark.png docs/README.md' lib/nfcore_external_java_deps.jar lib/NfcoreSchema.groovy @@ -91,8 +93,10 @@ def files_unchanged(self): [os.path.join("assets", "email_template.html")], [os.path.join("assets", "email_template.txt")], [os.path.join("assets", "sendmail_template.txt")], - [os.path.join("assets", f"nf-core-{short_name}_logo.png")], - [os.path.join("docs", "images", f"nf-core-{short_name}_logo.png")], + [os.path.join("assets", f"nf-core-{short_name}_logo_light.png")], + [os.path.join("assets", f"nf-core-{short_name}_logo_dark.png")], + [os.path.join("docs", "images", f"nf-core-{short_name}_logo_light.png")], + [os.path.join("docs", "images", f"nf-core-{short_name}_logo_dark.png")], [os.path.join("docs", "README.md")], [os.path.join("lib", "nfcore_external_java_deps.jar")], [os.path.join("lib", "NfcoreSchema.groovy")], diff --git a/nf_core/pipeline-template/README.md b/nf_core/pipeline-template/README.md index 6003672ebc..193a994bbd 100644 --- a/nf_core/pipeline-template/README.md +++ b/nf_core/pipeline-template/README.md @@ -1,4 +1,5 @@ -# ![{{ name }}](docs/images/{{ name_noslash }}_logo.png) +# ![{{ name }}](docs/images/{{ name_noslash }}_logo_light.png#gh-light-mode-only) +# ![{{ name }}](docs/images/{{ name_noslash }}_logo_dark.png#gh-dark-mode-only) [![GitHub Actions CI Status](https://github.com/{{ name }}/workflows/nf-core%20CI/badge.svg)](https://github.com/{{ name }}/actions?query=workflow%3A%22nf-core+CI%22) [![GitHub Actions Linting Status](https://github.com/{{ name }}/workflows/nf-core%20linting/badge.svg)](https://github.com/{{ name }}/actions?query=workflow%3A%22nf-core+linting%22) diff --git a/nf_core/pipeline-template/assets/sendmail_template.txt b/nf_core/pipeline-template/assets/sendmail_template.txt index 1abf5b0ab5..3e59cd2d6d 100644 --- a/nf_core/pipeline-template/assets/sendmail_template.txt +++ b/nf_core/pipeline-template/assets/sendmail_template.txt @@ -12,9 +12,9 @@ $email_html Content-Type: image/png;name="{{ name_noslash }}_logo.png" Content-Transfer-Encoding: base64 Content-ID: -Content-Disposition: inline; filename="{{ name_noslash }}_logo.png" +Content-Disposition: inline; filename="{{ name_noslash }}_logo_light.png" -<% out << new File("$projectDir/assets/{{ name_noslash }}_logo.png"). +<% out << new File("$projectDir/assets/{{ name_noslash }}_logo_light.png"). bytes. encodeBase64(). toString(). From 6709dc9ce008336aebbcbbb3967b933e5982c0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6rtenhuber?= Date: Thu, 2 Dec 2021 17:17:50 +0100 Subject: [PATCH 222/266] fix linting errors --- README.md | 3 +-- nf_core/pipeline-template/README.md | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9981755fc5..fc02bce284 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -# ![nf-core/tools](docs/images/nfcore-tools_logo_light.png#gh-light-mode-only) -# ![nf-core/tools](docs/images/nfcore-tools_logo_dark.png#gh-dark-mode-only) +# ![nf-core/tools](docs/images/nfcore-tools_logo_light.png#gh-light-mode-only) ![nf-core/tools](docs/images/nfcore-tools_logo_dark.png#gh-dark-mode-only) [![Python tests](https://github.com/nf-core/tools/workflows/Python%20tests/badge.svg?branch=master&event=push)](https://github.com/nf-core/tools/actions?query=workflow%3A%22Python+tests%22+branch%3Amaster) [![codecov](https://codecov.io/gh/nf-core/tools/branch/master/graph/badge.svg)](https://codecov.io/gh/nf-core/tools) diff --git a/nf_core/pipeline-template/README.md b/nf_core/pipeline-template/README.md index 193a994bbd..e7e539d990 100644 --- a/nf_core/pipeline-template/README.md +++ b/nf_core/pipeline-template/README.md @@ -1,5 +1,4 @@ -# ![{{ name }}](docs/images/{{ name_noslash }}_logo_light.png#gh-light-mode-only) -# ![{{ name }}](docs/images/{{ name_noslash }}_logo_dark.png#gh-dark-mode-only) +# ![{{ name }}](docs/images/{{ name_noslash }}_logo_light.png#gh-light-mode-only) ![{{ name }}](docs/images/{{ name_noslash }}_logo_dark.png#gh-dark-mode-only) [![GitHub Actions CI Status](https://github.com/{{ name }}/workflows/nf-core%20CI/badge.svg)](https://github.com/{{ name }}/actions?query=workflow%3A%22nf-core+CI%22) [![GitHub Actions Linting Status](https://github.com/{{ name }}/workflows/nf-core%20linting/badge.svg)](https://github.com/{{ name }}/actions?query=workflow%3A%22nf-core+linting%22) From 3e44f0718e86dd2c252c565f660b783ffcc3f87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6rtenhuber?= Date: Thu, 2 Dec 2021 17:33:02 +0100 Subject: [PATCH 223/266] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcd034f39c..6958e7bc13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Template +* Update repo logos to utilize [GitHub's `#gh-light/dark-mode-only`](https://docs.github.com/en/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#specifying-the-theme-an-image-is-shown-to), to switch between logos optimized for light or dark themes. * Solve circular import when importing `nf_core.modules.lint` * Disable cache in `nf_core.utils.fetch_wf_config` while performing `test_wf_use_local_configs`. * Modify software version channel handling to support multiple software version emissions (e.g. from mulled containers), and multiple software versions. From 31df888f428492e5524e622aa95e61a7d4921d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6rtenhuber?= Date: Fri, 3 Dec 2021 09:50:17 +0100 Subject: [PATCH 224/266] add lint warning to remove old style repo logo --- nf_core/lint/files_exist.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nf_core/lint/files_exist.py b/nf_core/lint/files_exist.py index 8800157a7f..2687f35563 100644 --- a/nf_core/lint/files_exist.py +++ b/nf_core/lint/files_exist.py @@ -79,6 +79,7 @@ def files_exist(self): conf/aws.config .github/workflows/push_dockerhub.yml .github/ISSUE_TEMPLATE/bug_report.md + docs/images/nf-core-PIPELINE_logo.png Files that *should not* be present: @@ -159,6 +160,7 @@ def files_exist(self): os.path.join(".github", "workflows", "push_dockerhub.yml"), os.path.join(".github", "ISSUE_TEMPLATE", "bug_report.md"), os.path.join(".github", "ISSUE_TEMPLATE", "feature_request.md"), + os.path.join("docs", "images", "nf-core-PIPELINE_logo.png"), ] files_warn_ifexists = [".travis.yml"] From e1a8e3167a45d1f7e3feac34469ea9b67bd5ecd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6rtenhuber?= Date: Fri, 3 Dec 2021 11:21:59 +0100 Subject: [PATCH 225/266] make linting pass (only check for light logo in email template) --- nf_core/create.py | 2 +- nf_core/lint/files_exist.py | 4 +--- nf_core/lint/files_unchanged.py | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/nf_core/create.py b/nf_core/create.py index 763673a312..02dd50a1bc 100644 --- a/nf_core/create.py +++ b/nf_core/create.py @@ -151,7 +151,7 @@ def make_pipeline_logo(self): email_logo_path = f"{self.outdir}/assets/{self.name_noslash}_logo_light.png" os.makedirs(os.path.dirname(email_logo_path), exist_ok=True) log.debug(f"Writing logo to '{email_logo_path}'") - r = requests.get(f"{logo_url}?w=400") + r = requests.get(f"{logo_url}&w=400") with open(email_logo_path, "wb") as fh: fh.write(r.content) for theme in ["dark", "light"]: diff --git a/nf_core/lint/files_exist.py b/nf_core/lint/files_exist.py index 2687f35563..23759b8989 100644 --- a/nf_core/lint/files_exist.py +++ b/nf_core/lint/files_exist.py @@ -35,7 +35,6 @@ def files_exist(self): assets/email_template.html assets/email_template.txt assets/nf-core-PIPELINE_logo_light.png - assets/nf-core-PIPELINE_logo_dark.png assets/sendmail_template.txt conf/modules.config conf/test.config @@ -123,7 +122,6 @@ def files_exist(self): [os.path.join("assets", "email_template.txt")], [os.path.join("assets", "sendmail_template.txt")], [os.path.join("assets", f"nf-core-{short_name}_logo_light.png")], - [os.path.join("assets", f"nf-core-{short_name}_logo_dark.png")], [os.path.join("conf", "modules.config")], [os.path.join("conf", "test.config")], [os.path.join("conf", "test_full.config")], @@ -160,7 +158,7 @@ def files_exist(self): os.path.join(".github", "workflows", "push_dockerhub.yml"), os.path.join(".github", "ISSUE_TEMPLATE", "bug_report.md"), os.path.join(".github", "ISSUE_TEMPLATE", "feature_request.md"), - os.path.join("docs", "images", "nf-core-PIPELINE_logo.png"), + os.path.join("docs", "images", f"nf-core-{short_name}_logo.png"), ] files_warn_ifexists = [".travis.yml"] diff --git a/nf_core/lint/files_unchanged.py b/nf_core/lint/files_unchanged.py index 806e201f46..6795e62930 100644 --- a/nf_core/lint/files_unchanged.py +++ b/nf_core/lint/files_unchanged.py @@ -32,7 +32,6 @@ def files_unchanged(self): assets/email_template.html assets/email_template.txt assets/nf-core-PIPELINE_logo_light.png - assets/nf-core-PIPELINE_logo_dark.png assets/sendmail_template.txt CODE_OF_CONDUCT.md docs/images/nf-core-PIPELINE_logo_light.png @@ -94,7 +93,6 @@ def files_unchanged(self): [os.path.join("assets", "email_template.txt")], [os.path.join("assets", "sendmail_template.txt")], [os.path.join("assets", f"nf-core-{short_name}_logo_light.png")], - [os.path.join("assets", f"nf-core-{short_name}_logo_dark.png")], [os.path.join("docs", "images", f"nf-core-{short_name}_logo_light.png")], [os.path.join("docs", "images", f"nf-core-{short_name}_logo_dark.png")], [os.path.join("docs", "README.md")], From 932b97107e7675e70c4c6e8f81a7b62eeb894a48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6rtenhuber?= Date: Fri, 3 Dec 2021 11:30:33 +0100 Subject: [PATCH 226/266] mention removal of old logos in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48d4a88837..4a6f7926e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Template -* Update repo logos to utilize [GitHub's `#gh-light/dark-mode-only`](https://docs.github.com/en/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#specifying-the-theme-an-image-is-shown-to), to switch between logos optimized for light or dark themes. +* Update repo logos to utilize [GitHub's `#gh-light/dark-mode-only`](https://docs.github.com/en/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#specifying-the-theme-an-image-is-shown-to), to switch between logos optimized for light or dark themes. The old repo logos have to be removed (in `docs/images` and `assets/`). * Deal with authentication with private repositories * Bump minimun Nextflow version to 21.10.3 * Convert pipeline template to updated Nextflow DSL2 syntax From 0c8d1d7a5d2b73c26c6442297bb6a57fce969816 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Mon, 6 Dec 2021 12:46:54 +0100 Subject: [PATCH 227/266] Don't assume that the module repo branch is master if not set --- nf_core/modules/modules_command.py | 8 ++++++-- nf_core/modules/modules_repo.py | 19 +++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/nf_core/modules/modules_command.py b/nf_core/modules/modules_command.py index ba8bba9ef1..866bffabb9 100644 --- a/nf_core/modules/modules_command.py +++ b/nf_core/modules/modules_command.py @@ -161,6 +161,7 @@ def modules_json_up_to_date(self): modules_repo.get_modules_file_tree() install_folder = [modules_repo.owner, modules_repo.repo] except LookupError as e: + log.warn(f"Could not get module's file tree for '{repo}': {e}") remove_from_mod_json[repo] = list(modules.keys()) continue @@ -169,6 +170,9 @@ def modules_json_up_to_date(self): if sha is None: if repo not in remove_from_mod_json: remove_from_mod_json[repo] = [] + log.warn( + f"Could not find git SHA for module '{module}' in '{repo}' - removing from modules.json" + ) remove_from_mod_json[repo].append(module) continue module_dir = os.path.join(self.dir, "modules", *install_folder, module) @@ -228,8 +232,8 @@ def _s(some_list): return "" if len(some_list) == 1 else "s" log.info( - f"Could not determine 'git_sha' for module{_s(failed_to_find_commit_sha)}: '{', '.join(failed_to_find_commit_sha)}'." - f"\nPlease try to install a newer version of {'this' if len(failed_to_find_commit_sha) == 1 else 'these'} module{_s(failed_to_find_commit_sha)}." + f"Could not determine 'git_sha' for module{_s(failed_to_find_commit_sha)}: {', '.join(failed_to_find_commit_sha)}." + f"\nPlease try to install a newer version of {'this' if len(failed_to_find_commit_sha) == 1 else 'these'} module{_s(failed_to_find_commit_sha)}." ) self.dump_modules_json(fresh_mod_json) diff --git a/nf_core/modules/modules_repo.py b/nf_core/modules/modules_repo.py index 9a9c4f032c..5f2f567d9c 100644 --- a/nf_core/modules/modules_repo.py +++ b/nf_core/modules/modules_repo.py @@ -16,12 +16,17 @@ class ModulesRepo(object): so that this can be used in the same way by all sub-commands. """ - def __init__(self, repo="nf-core/modules", branch="master"): + def __init__(self, repo="nf-core/modules", branch=None): self.name = repo self.branch = branch # Verify that the repo seems to be correctly configured - if self.name != "nf-core/modules" or self.branch != "master": + if self.name != "nf-core/modules" or self.branch: + + # Get the default branch if not set + if not self.branch: + self.get_default_branch() + try: self.verify_modules_repo() except LookupError: @@ -31,6 +36,16 @@ def __init__(self, repo="nf-core/modules", branch="master"): self.modules_file_tree = {} self.modules_avail_module_names = [] + def get_default_branch(self): + """Get the default branch for a GitHub repo""" + api_url = f"https://api.github.com/repos/{self.name}" + response = requests.get(api_url, auth=nf_core.utils.github_api_auto_auth()) + if response.status_code == 200: + self.branch = response.json()["default_branch"] + log.debug(f"Found default branch to be '{self.branch}'") + else: + raise LookupError(f"Could not find repository '{self.name}' on GitHub") + def verify_modules_repo(self): # Check if name seems to be well formed From 82b08198a4b7585569525b02ad0c9e3c62c4ba31 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Mon, 6 Dec 2021 12:48:20 +0100 Subject: [PATCH 228/266] Set to master without checking if using nf-core/modules --- nf_core/modules/modules_repo.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nf_core/modules/modules_repo.py b/nf_core/modules/modules_repo.py index 5f2f567d9c..a59e3262aa 100644 --- a/nf_core/modules/modules_repo.py +++ b/nf_core/modules/modules_repo.py @@ -20,6 +20,10 @@ def __init__(self, repo="nf-core/modules", branch=None): self.name = repo self.branch = branch + # Don't bother fetching default branch if we're using nf-core + if not self.branch and self.name == "nf-core/modules": + self.branch = "master" + # Verify that the repo seems to be correctly configured if self.name != "nf-core/modules" or self.branch: From aff69ca0687d869d1f38fab437229ffa164236f7 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Mon, 6 Dec 2021 13:09:19 +0100 Subject: [PATCH 229/266] Fix linting for new syntax --- nf_core/lint/actions_ci.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nf_core/lint/actions_ci.py b/nf_core/lint/actions_ci.py index 48056a9422..c87931b77a 100644 --- a/nf_core/lint/actions_ci.py +++ b/nf_core/lint/actions_ci.py @@ -130,12 +130,12 @@ def actions_ci(self): # Check that we are testing the minimum nextflow version try: - matrix = ciwf["jobs"]["test"]["strategy"]["matrix"]["nxf_ver"] - assert any([f"NXF_VER={self.minNextflowVersion}" in matrix]) + matrix = ciwf["jobs"]["test"]["strategy"]["matrix"]["include"] + assert any([i["NXF_VER"] == self.minNextflowVersion for i in matrix]) except (KeyError, TypeError): failed.append("'.github/workflows/ci.yml' does not check minimum NF version") except AssertionError: - failed.append("Minimum NF version in '.github/workflows/ci.yml' different to pipeline's manifest") + failed.append("Minimum NF version in '.github/workflows/ci.yml' is not tested") else: passed.append("'.github/workflows/ci.yml' checks minimum NF version") From 518a52706bce44cc23caafec37856342a5e5da71 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Mon, 6 Dec 2021 13:11:28 +0100 Subject: [PATCH 230/266] Linting: better error message --- nf_core/lint/actions_ci.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nf_core/lint/actions_ci.py b/nf_core/lint/actions_ci.py index c87931b77a..e9adf706b7 100644 --- a/nf_core/lint/actions_ci.py +++ b/nf_core/lint/actions_ci.py @@ -135,7 +135,9 @@ def actions_ci(self): except (KeyError, TypeError): failed.append("'.github/workflows/ci.yml' does not check minimum NF version") except AssertionError: - failed.append("Minimum NF version in '.github/workflows/ci.yml' is not tested") + failed.append( + f"Minimum pipeline NF version '{self.minNextflowVersion}' is not tested in '.github/workflows/ci.yml'" + ) else: passed.append("'.github/workflows/ci.yml' checks minimum NF version") From a42e530f0b30050226a0d9e6c48655aa7a8ed312 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Mon, 6 Dec 2021 13:20:19 +0100 Subject: [PATCH 231/266] Update Nextflow bump-version --- nf_core/bump_version.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/nf_core/bump_version.py b/nf_core/bump_version.py index 29f3a39f90..94bc17b66c 100644 --- a/nf_core/bump_version.py +++ b/nf_core/bump_version.py @@ -81,9 +81,9 @@ def bump_nextflow_version(pipeline_obj, new_version): pipeline_obj, [ ( - # example: nxf_ver: ['20.04.0', ''] - r"nxf_ver: \[[\'\"]{}[\'\"], [\'\"][\'\"]\]".format(current_version.replace(".", r"\.")), - "nxf_ver: ['{}', '']".format(new_version), + # example: - NXF_VER: '20.04.0' + r"- NXF_VER: [\'\"]{}[\'\"]".format(current_version.replace(".", r"\.")), + "- NXF_VER: '{}'".format(new_version), ) ], ) @@ -97,14 +97,11 @@ def bump_nextflow_version(pipeline_obj, new_version): r"nextflow%20DSL2-%E2%89%A5{}-23aa62.svg".format(current_version.replace(".", r"\.")), "nextflow%20DSL2-%E2%89%A5{}-23aa62.svg".format(new_version), ), - ( - # Replace links to 'nf-co.re' installation page with links to Nextflow installation page - r"https://nf-co.re/usage/installation", - "https://www.nextflow.io/docs/latest/getstarted.html#installation", - ), ( # example: 1. Install [`Nextflow`](https://www.nextflow.io/docs/latest/getstarted.html#installation) (`>=20.04.0`) - r"1\.\s*Install\s*\[`Nextflow`\]\(y\)\s*\(`>={}`\)".format(current_version.replace(".", r"\.")), + r"1\.\s*Install\s*\[`Nextflow`\]\(https:\/\/www\.nextflow\.io\/docs\/latest\/getstarted\.html#installation\)\s*\(`>={}`\)".format( + current_version.replace(".", r"\.") + ), "1. Install [`Nextflow`](https://www.nextflow.io/docs/latest/getstarted.html#installation) (`>={}`)".format( new_version ), From d7f186283db098d9ec5eb5c7a8cdff24b5408812 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Mon, 6 Dec 2021 13:23:41 +0100 Subject: [PATCH 232/266] Update pytests --- tests/lint/actions_ci.py | 2 +- tests/test_bump_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lint/actions_ci.py b/tests/lint/actions_ci.py index 13cdfbbc00..3329089975 100644 --- a/tests/lint/actions_ci.py +++ b/tests/lint/actions_ci.py @@ -23,7 +23,7 @@ def test_actions_ci_fail_wrong_nf(self): self.lint_obj._load() self.lint_obj.minNextflowVersion = "1.2.3" results = self.lint_obj.actions_ci() - assert results["failed"] == ["Minimum NF version in '.github/workflows/ci.yml' different to pipeline's manifest"] + assert results["failed"] == ["Minimum pipeline NF version '1.2.3' is not tested in '.github/workflows/ci.yml'"] def test_actions_ci_fail_wrong_docker_ver(self): diff --git a/tests/test_bump_version.py b/tests/test_bump_version.py index 39840fe6d1..b9fc27a153 100644 --- a/tests/test_bump_version.py +++ b/tests/test_bump_version.py @@ -71,7 +71,7 @@ def test_bump_nextflow_version(datafiles): # Check .github/workflows/ci.yml with open(new_pipeline_obj._fp(".github/workflows/ci.yml")) as fh: ci_yaml = yaml.safe_load(fh) - assert ci_yaml["jobs"]["test"]["strategy"]["matrix"]["nxf_ver"][0] == "NXF_VER=21.10.3" + assert ci_yaml["jobs"]["test"]["strategy"]["matrix"]["include"][0]["NXF_VER"] == "21.10.3" # Check README.md with open(new_pipeline_obj._fp("README.md")) as fh: From 58c37cff9151a201eb79b6128b172dc9600c18b3 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 9 Dec 2021 13:14:01 +0100 Subject: [PATCH 233/266] Schema linting - check that schema default matches nextflow.config value --- nf_core/schema.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/nf_core/schema.py b/nf_core/schema.py index de84f80002..c3825b4ba6 100644 --- a/nf_core/schema.py +++ b/nf_core/schema.py @@ -258,9 +258,7 @@ def validate_default_params(self): if param in params_ignore: continue if param in self.pipeline_params: - self.validate_config_default_parameter( - param, group_properties[param]["type"], self.pipeline_params[param] - ) + self.validate_config_default_parameter(param, group_properties[param], self.pipeline_params[param]) else: self.invalid_nextflow_config_default_parameters[param] = "Not in pipeline parameters" @@ -272,38 +270,52 @@ def validate_default_params(self): continue if param in self.pipeline_params: self.validate_config_default_parameter( - param, ungrouped_properties[param]["type"], self.pipeline_params[param] + param, ungrouped_properties[param], self.pipeline_params[param] ) else: self.invalid_nextflow_config_default_parameters[param] = "Not in pipeline parameters" - def validate_config_default_parameter(self, param, schema_default_type, config_default): + def validate_config_default_parameter(self, param, schema_param, config_default): """ Assure that default parameters in the nextflow.config are correctly set by comparing them to their type in the schema """ + + # If we have a default in the schema, check it matches the config + if "default" in schema_param and ( + (schema_param["type"] == "boolean" and str(config_default).lower() != str(schema_param["default"]).lower()) + and (str(schema_param["default"]) != str(config_default).strip('"').strip("'")) + ): + # Check that we are not deferring the execution of this parameter in the schema default with squiggly brakcets + if schema_param["type"] != "string" or "{" not in schema_param["default"]: + self.invalid_nextflow_config_default_parameters[ + param + ] = f"Schema default (`{schema_param['default']}`) does not match the config default (`{config_default}`)" + return + # if default is null, we're good if config_default == "null": return - # else check for allowed defaults - if schema_default_type == "string": + + # Check variable types in nextflow.config + if schema_param["type"] == "string": if str(config_default) in ["false", "true", "''"]: self.invalid_nextflow_config_default_parameters[ param ] = f"String should not be set to `{config_default}`" - if schema_default_type == "boolean": + if schema_param["type"] == "boolean": if not str(config_default) in ["false", "true"]: self.invalid_nextflow_config_default_parameters[ param ] = f"Booleans should only be true or false, not `{config_default}`" - if schema_default_type == "integer": + if schema_param["type"] == "integer": try: int(config_default) except ValueError: self.invalid_nextflow_config_default_parameters[ param ] = f"Does not look like an integer: `{config_default}`" - if schema_default_type == "number": + if schema_param["type"] == "number": try: float(config_default) except ValueError: From df49066894d7ee0057ecd52c3c23a7997c654045 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 9 Dec 2021 13:17:19 +0100 Subject: [PATCH 234/266] Changelog update --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f3cb5e1f6..0b99988184 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,8 @@ ### General * Made lint check for parameters defaults stricter [[#992](https://github.com/nf-core/tools/issues/992)] - * Defaults must now match the variable type specified in the schema + * Default values in `nextflow.config` must match the defaults given in the schema (anything with `{` in, or in `main.nf` is ignored) + * Defaults in `nextflow.config` must now match the variable _type_ specified in the schema * If you want the parameter to not have a default value, use `null` * Strings set to `false` or an empty string in `nextflow.config` will now fail linting * Bump minimun Nextflow version to 21.10.3 From 5130cc4dba8612d99f778a752dbec33b3c856334 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Thu, 9 Dec 2021 13:15:07 +0000 Subject: [PATCH 235/266] Bump version to 2.2 --- CHANGELOG.md | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41b57e7e2c..0614fa089f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # nf-core/tools: Changelog -## v2.2dev +## [v2.2 - Lead Liger](https://github.com/nf-core/tools/releases/tag/2.2) - [2021-12-09] ### Template diff --git a/setup.py b/setup.py index 513f9d9fec..86252375b1 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages -version = "2.2dev" +version = "2.2" with open("README.md") as f: readme = f.read() From 64285182f6aba48a1548e288abbea5e8a4af739d Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 9 Dec 2021 23:35:58 +0100 Subject: [PATCH 236/266] Update miniconda base in base.Dockerfile --- base.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base.Dockerfile b/base.Dockerfile index 8d3fe9941e..fee0797a1c 100644 --- a/base.Dockerfile +++ b/base.Dockerfile @@ -1,4 +1,4 @@ -FROM continuumio/miniconda3:4.9.2 +FROM continuumio/miniconda3:4.10.3 LABEL authors="phil.ewels@scilifelab.se,alexander.peltzer@boehringer-ingelheim.com" \ description="Docker image containing base requirements for the nfcore pipelines" From 14ed38233c3588b0262fe5dc036655b57d38aebf Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 9 Dec 2021 23:43:15 +0100 Subject: [PATCH 237/266] Strip DSL1 base-image Dockerfile and build --- .github/workflows/push_dockerhub_dev.yml | 13 +++++-------- .github/workflows/push_dockerhub_release.yml | 10 ++++------ CHANGELOG.md | 1 + tools.Dockerfile => Dockerfile | 0 base.Dockerfile | 13 ------------- 5 files changed, 10 insertions(+), 27 deletions(-) rename tools.Dockerfile => Dockerfile (100%) delete mode 100644 base.Dockerfile diff --git a/.github/workflows/push_dockerhub_dev.yml b/.github/workflows/push_dockerhub_dev.yml index 6d8b2457b7..e0c26f2f4e 100644 --- a/.github/workflows/push_dockerhub_dev.yml +++ b/.github/workflows/push_dockerhub_dev.yml @@ -3,31 +3,28 @@ name: nf-core Docker push (dev) # Runs on nf-core repo releases and push event to 'dev' branch (PR merges) on: push: - branches: - - dev + branches: [dev] jobs: push_dockerhub: name: Push new Docker image to Docker Hub (dev) runs-on: ubuntu-latest # Only run for the nf-core repo, for releases and merged PRs - if: ${{ github.repository == 'nf-core/tools'}} + if: ${{ github.repository == 'nf-core/tools' }} env: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} DOCKERHUB_PASS: ${{ secrets.DOCKERHUB_PASS }} strategy: - matrix: - image: [base, tools] fail-fast: false steps: - - name: Check out tools code + - name: Check out code uses: actions/checkout@v2 - name: Build new docker image - run: docker build --no-cache . -t nfcore/${{ matrix.image }}:dev -f ${{ matrix.image }}.Dockerfile + run: docker build --no-cache . -t nfcore/tools:dev - name: Push Docker image to DockerHub (dev) run: | echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin - docker push nfcore/${{ matrix.image }}:dev + docker push nfcore/tools:dev diff --git a/.github/workflows/push_dockerhub_release.yml b/.github/workflows/push_dockerhub_release.yml index 7a58d02938..2aebb34400 100644 --- a/.github/workflows/push_dockerhub_release.yml +++ b/.github/workflows/push_dockerhub_release.yml @@ -15,19 +15,17 @@ jobs: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} DOCKERHUB_PASS: ${{ secrets.DOCKERHUB_PASS }} strategy: - matrix: - image: [base, tools] fail-fast: false steps: - name: Check out code uses: actions/checkout@v2 - name: Build new docker image - run: docker build --no-cache . -t nfcore/${{ matrix.image }}:latest -f ${{ matrix.image }}.Dockerfile + run: docker build --no-cache . -t nfcore/tools:latest - name: Push Docker image to DockerHub (release) run: | echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin - docker push nfcore/${{ matrix.image }}:latest - docker tag nfcore/${{ matrix.image }}:latest nfcore/${{ matrix.image }}:${{ github.event.release.tag_name }} - docker push nfcore/${{ matrix.image }}:${{ github.event.release.tag_name }} + docker push nfcore/tools:latest + docker tag nfcore/tools:latest nfcore/tools:${{ github.event.release.tag_name }} + docker push nfcore/tools:${{ github.event.release.tag_name }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 41b57e7e2c..00d089b305 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ * Run CI test used to create and lint/run the pipeline template with minimum and latest edge release of NF ([#1304](https://github.com/nf-core/tools/issues/1304)) * New YAML issue templates for tools bug reports and feature requests, with a much richer interface ([#1165](https://github.com/nf-core/tools/pull/1165)) * Handle synax errors in Nextflow config nicely when running `nf-core schema build` ([#1267](https://github.com/nf-core/tools/pull/1267)) +* Remove base `Dockerfile` used for DSL1 pipeline container builds ### Modules diff --git a/tools.Dockerfile b/Dockerfile similarity index 100% rename from tools.Dockerfile rename to Dockerfile diff --git a/base.Dockerfile b/base.Dockerfile deleted file mode 100644 index fee0797a1c..0000000000 --- a/base.Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM continuumio/miniconda3:4.10.3 -LABEL authors="phil.ewels@scilifelab.se,alexander.peltzer@boehringer-ingelheim.com" \ - description="Docker image containing base requirements for the nfcore pipelines" - -# Install procps so that Nextflow can poll CPU usage and -# deep clean the apt cache to reduce image/layer size -RUN apt-get update \ - && apt-get install -y procps \ - && apt-get clean -y && rm -rf /var/lib/apt/lists/* - -# Instruct R processes to use these empty files instead of clashing with a local version -RUN touch .Rprofile -RUN touch .Renviron From 6524a3e2a87f0585231fe7d315909fc629d18a74 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 9 Dec 2021 23:57:31 +0100 Subject: [PATCH 238/266] Remove contents of tests/__init__.py --- tests/__init__.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index b3e4769de7..e69de29bb2 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,7 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Tue Nov 9 13:46:10 2021 - -@author: Paolo Cozzi -""" From d4d0439b94e2f0f70778cea2240ec9c3b021f092 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 10 Dec 2021 00:15:19 +0100 Subject: [PATCH 239/266] Module lint: Fail instead of warn if local copy edited --- CHANGELOG.md | 1 + nf_core/modules/lint/module_changes.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a172ca097c..2b03ffaef1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ * Check if README is from modules repo * Update module template to DSL2 v2.0 (remove `functions.nf` from modules template and updating `main.nf` ([#1289](https://github.com/nf-core/tools/pull/)) * Substitute get process/module name custom functions in module `main.nf` using template replacement ([#1284](https://github.com/nf-core/tools/issues/1284)) +* Linting now fails instead of warning if a local copy of a module does not match the remote ([#1313](https://github.com/nf-core/tools/issues/1313)) ## [v2.1 - Zinc Zebra](https://github.com/nf-core/tools/releases/tag/2.1) - [2021-07-27] diff --git a/nf_core/modules/lint/module_changes.py b/nf_core/modules/lint/module_changes.py index 773e5db408..f36035e2cd 100644 --- a/nf_core/modules/lint/module_changes.py +++ b/nf_core/modules/lint/module_changes.py @@ -49,10 +49,10 @@ def module_changes(module_lint_object, module): remote_copy = r.content.decode("utf-8") if local_copy != remote_copy: - module.warned.append( + module.failed.append( ( "check_local_copy", - "Local copy of module outdated", + "Local copy of module does not match remote", f"{os.path.join(module.module_dir, f)}", ) ) From ea9f8d2a59acdb925b8e651120256a42f2b993cf Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 10 Dec 2021 00:27:57 +0100 Subject: [PATCH 240/266] Modules lint: Check for md5sums of empty files --- CHANGELOG.md | 1 + nf_core/modules/lint/module_tests.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a172ca097c..a69e4c4159 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ * Check if README is from modules repo * Update module template to DSL2 v2.0 (remove `functions.nf` from modules template and updating `main.nf` ([#1289](https://github.com/nf-core/tools/pull/)) * Substitute get process/module name custom functions in module `main.nf` using template replacement ([#1284](https://github.com/nf-core/tools/issues/1284)) +* Check test YML file for md5sums corresponding to empty files ([#1302](https://github.com/nf-core/tools/issues/1302)) ## [v2.1 - Zinc Zebra](https://github.com/nf-core/tools/releases/tag/2.1) - [2021-07-27] diff --git a/nf_core/modules/lint/module_tests.py b/nf_core/modules/lint/module_tests.py index fad25d20ac..7bef0112d1 100644 --- a/nf_core/modules/lint/module_tests.py +++ b/nf_core/modules/lint/module_tests.py @@ -52,11 +52,31 @@ def module_tests(module_lint_object, module): if not tag in [module.module_name, module.module_name.split("/")[0]]: all_tags_correct = False + # Look for md5sums of empty files + for tfile in test.get("files", []): + if tfile.get("md5sum") == "d41d8cd98f00b204e9800998ecf8427e": + module.warned.append( + ( + "test_yml_md5sum", + "md5sum for empty file found: d41d8cd98f00b204e9800998ecf8427e", + module.test_yml, + ) + ) + if tfile.get("md5sum") == "7029066c27ac6f5ef18d660d5741979a": + module.warned.append( + ( + "test_yml_md5sum", + "md5sum for compressed empty file found: 7029066c27ac6f5ef18d660d5741979a", + module.test_yml, + ) + ) + if all_tags_correct: module.passed.append(("test_yml_tags", "tags adhere to guidelines", module.test_yml)) else: module.failed.append(("test_yml_tags", "tags do not adhere to guidelines", module.test_yml)) + # Test that the file exists module.passed.append(("test_yml_exists", "Test `test.yml` exists", module.test_yml)) except FileNotFoundError: module.failed.append(("test_yml_exists", "Test `test.yml` does not exist", module.test_yml)) From a501ba2c14529a7c478c3415ff526865a50d4256 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 10 Dec 2021 00:32:01 +0100 Subject: [PATCH 241/266] Modules test builder: Don't add md5s for empty files --- nf_core/modules/test_yml_builder.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nf_core/modules/test_yml_builder.py b/nf_core/modules/test_yml_builder.py index 9b05a97070..c305e6ffc6 100644 --- a/nf_core/modules/test_yml_builder.py +++ b/nf_core/modules/test_yml_builder.py @@ -205,7 +205,11 @@ def create_test_file_dict(self, results_dir): elem_md5 = self._md5(elem) # Switch out the results directory path with the expected 'output' directory elem = elem.replace(results_dir, "output") - test_files.append({"path": elem, "md5sum": elem_md5}) + test_file = {"path": elem} + # Only add the md5 if it's not for an empty file / compressed empty file + if elem_md5 != "d41d8cd98f00b204e9800998ecf8427e" and elem_md5 != "7029066c27ac6f5ef18d660d5741979a": + test_file["md5sum"] = elem_md5 + test_files.append(test_file) test_files = sorted(test_files, key=operator.itemgetter("path")) From c62517e1a3c1240e8bbbd7a6106bdcb910ce2b87 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Fri, 10 Dec 2021 01:14:36 +0000 Subject: [PATCH 242/266] Fix bug when printing help for enums --- nf_core/pipeline-template/lib/NfcoreSchema.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/lib/NfcoreSchema.groovy b/nf_core/pipeline-template/lib/NfcoreSchema.groovy index 3b02c0a217..40ab65f205 100755 --- a/nf_core/pipeline-template/lib/NfcoreSchema.groovy +++ b/nf_core/pipeline-template/lib/NfcoreSchema.groovy @@ -362,7 +362,7 @@ class NfcoreSchema { } } for (ex in causingExceptions) { - printExceptions(ex, params_json, log) + printExceptions(ex, params_json, log, enums) } } From d60fc5370302dcda1f52e7c7644b7fb296e4c8cf Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Fri, 10 Dec 2021 12:19:17 +0000 Subject: [PATCH 243/266] Remove joinModuleArgs function from template --- nf_core/pipeline-template/lib/Utils.groovy | 7 ------- 1 file changed, 7 deletions(-) diff --git a/nf_core/pipeline-template/lib/Utils.groovy b/nf_core/pipeline-template/lib/Utils.groovy index 18173e9850..1b88aec0ea 100755 --- a/nf_core/pipeline-template/lib/Utils.groovy +++ b/nf_core/pipeline-template/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(' ') - } } From 06e0403840557737e5318459af6efe0768676853 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Fri, 10 Dec 2021 14:16:47 +0000 Subject: [PATCH 244/266] Fix wording in bug_report.yml --- nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.yml b/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.yml index 246362bf55..e904de573f 100644 --- a/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/nf_core/pipeline-template/.github/ISSUE_TEMPLATE/bug_report.yml @@ -36,7 +36,7 @@ body: attributes: label: Relevant files description: | - Please upload (drag and drop) and relevant files. Make into a `.zip` file if the extension is not allowed. + 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 From 9ae5ddcb78b8cc1c8231d005782293b1e2ce7861 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Tue, 14 Dec 2021 07:45:38 +0000 Subject: [PATCH 245/266] Update test.config --- nf_core/pipeline-template/conf/test.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/pipeline-template/conf/test.config b/nf_core/pipeline-template/conf/test.config index e4419fd0d1..eb2a725c87 100644 --- a/nf_core/pipeline-template/conf/test.config +++ b/nf_core/pipeline-template/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 From f61613d60f849d3c632dcc64bee075f15a5f1591 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 14 Dec 2021 10:34:06 +0100 Subject: [PATCH 246/266] test-yaml-generate - don't check md5s, check for empty files and raise UserWarning --- nf_core/modules/test_yml_builder.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/nf_core/modules/test_yml_builder.py b/nf_core/modules/test_yml_builder.py index c305e6ffc6..aa66efd9c5 100644 --- a/nf_core/modules/test_yml_builder.py +++ b/nf_core/modules/test_yml_builder.py @@ -8,8 +8,10 @@ from rich.syntax import Syntax import errno +import gzip import hashlib import logging +import operator import os import questionary import re @@ -18,7 +20,6 @@ import subprocess import tempfile import yaml -import operator import nf_core.utils @@ -187,6 +188,15 @@ def build_single_test(self, entry_point): return ep_test + def check_if_empty_file(self, fname): + """Check if the file is empty, or compressed empty""" + if os.path.getsize(fname) == 0: + return True + with gzip.open(fname, "rb") as fh: + if fh.read() == b"": + return True + return False + def _md5(self, fname): """Generate md5 sum for file""" hash_md5 = hashlib.md5() @@ -202,14 +212,13 @@ def create_test_file_dict(self, results_dir): for root, dir, file in os.walk(results_dir): for elem in file: elem = os.path.join(root, elem) + # Check that this isn't an empty file + if self.check_if_empty_file(elem): + raise UserWarning(f"Empty file found: '{elem}'") elem_md5 = self._md5(elem) # Switch out the results directory path with the expected 'output' directory elem = elem.replace(results_dir, "output") - test_file = {"path": elem} - # Only add the md5 if it's not for an empty file / compressed empty file - if elem_md5 != "d41d8cd98f00b204e9800998ecf8427e" and elem_md5 != "7029066c27ac6f5ef18d660d5741979a": - test_file["md5sum"] = elem_md5 - test_files.append(test_file) + test_files.append({"path": elem, "md5sum": elem_md5}) test_files = sorted(test_files, key=operator.itemgetter("path")) From abe5c7ec19a07faa847c16494562ae2046285c2f Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 14 Dec 2021 10:42:09 +0100 Subject: [PATCH 247/266] Fix gzip empty file check --- nf_core/modules/test_yml_builder.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nf_core/modules/test_yml_builder.py b/nf_core/modules/test_yml_builder.py index aa66efd9c5..eb48b24bd8 100644 --- a/nf_core/modules/test_yml_builder.py +++ b/nf_core/modules/test_yml_builder.py @@ -192,9 +192,12 @@ def check_if_empty_file(self, fname): """Check if the file is empty, or compressed empty""" if os.path.getsize(fname) == 0: return True - with gzip.open(fname, "rb") as fh: - if fh.read() == b"": - return True + try: + with gzip.open(fname, "rb") as fh: + if fh.read() == b"": + return True + except gzip.BadGzipFile: + pass return False def _md5(self, fname): From 055d2d06f684e93683b780d2cbfec2c3d220b045 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 14 Dec 2021 10:43:00 +0100 Subject: [PATCH 248/266] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a69e4c4159..4ddab6c4e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ * Update module template to DSL2 v2.0 (remove `functions.nf` from modules template and updating `main.nf` ([#1289](https://github.com/nf-core/tools/pull/)) * Substitute get process/module name custom functions in module `main.nf` using template replacement ([#1284](https://github.com/nf-core/tools/issues/1284)) * Check test YML file for md5sums corresponding to empty files ([#1302](https://github.com/nf-core/tools/issues/1302)) +* Exit with an error if empty files are found when generating the test YAML file ([#1302](https://github.com/nf-core/tools/issues/1302)) ## [v2.1 - Zinc Zebra](https://github.com/nf-core/tools/releases/tag/2.1) - [2021-07-27] From 4efad123f689ccd56b8458b2ada6d9f1d9b7548b Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 14 Dec 2021 11:11:03 +0100 Subject: [PATCH 249/266] Don't exit with an error immediately, finish run then raise the UserWarning --- nf_core/modules/test_yml_builder.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/nf_core/modules/test_yml_builder.py b/nf_core/modules/test_yml_builder.py index eb48b24bd8..04378a8714 100644 --- a/nf_core/modules/test_yml_builder.py +++ b/nf_core/modules/test_yml_builder.py @@ -47,6 +47,7 @@ def __init__( self.module_test_main = None self.entry_points = [] self.tests = [] + self.errors = [] def run(self): """Run build steps""" @@ -58,6 +59,9 @@ def run(self): self.scrape_workflow_entry_points() self.build_all_tests() self.print_test_yml() + if len(self.errors) > 0: + errors = "\n - ".join(self.errors) + raise UserWarning(f"Ran, but found errors:\n - {errors}") def check_inputs(self): """Do more complex checks about supplied flags.""" @@ -209,19 +213,23 @@ def _md5(self, fname): md5sum = hash_md5.hexdigest() return md5sum - def create_test_file_dict(self, results_dir): + def create_test_file_dict(self, results_dir, is_repeat=False): """Walk through directory and collect md5 sums""" test_files = [] for root, dir, file in os.walk(results_dir): for elem in file: elem = os.path.join(root, elem) + test_file = {"path": elem} # Check that this isn't an empty file if self.check_if_empty_file(elem): - raise UserWarning(f"Empty file found: '{elem}'") - elem_md5 = self._md5(elem) + if not is_repeat: + self.errors.append(f"Empty file, skipping md5sum: '{os.path.basename(elem)}'") + else: + elem_md5 = self._md5(elem) + test_file["md5sum"] = elem_md5 # Switch out the results directory path with the expected 'output' directory elem = elem.replace(results_dir, "output") - test_files.append({"path": elem, "md5sum": elem_md5}) + test_files.append(test_file) test_files = sorted(test_files, key=operator.itemgetter("path")) @@ -253,11 +261,11 @@ def get_md5_sums(self, entry_point, command, results_dir=None, results_dir_repea # If test was repeated, compare the md5 sums if results_dir_repeat: - test_files_repeat = self.create_test_file_dict(results_dir=results_dir_repeat) + test_files_repeat = self.create_test_file_dict(results_dir=results_dir_repeat, is_repeat=True) # Compare both test.yml files for i in range(len(test_files)): - if not test_files[i]["md5sum"] == test_files_repeat[i]["md5sum"]: + if test_files[i].get("md5sum") and not test_files[i].get("md5sum") == test_files_repeat[i]["md5sum"]: test_files[i].pop("md5sum") test_files[i][ "contains" From 1eb80188ea1ab3d206b7e8eae7b7891ae454f806 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 14 Dec 2021 11:20:57 +0100 Subject: [PATCH 250/266] Try alternative gzip method to make pytest work on GitHub Actions --- nf_core/modules/test_yml_builder.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nf_core/modules/test_yml_builder.py b/nf_core/modules/test_yml_builder.py index 04378a8714..a95fcf74b7 100644 --- a/nf_core/modules/test_yml_builder.py +++ b/nf_core/modules/test_yml_builder.py @@ -197,8 +197,9 @@ def check_if_empty_file(self, fname): if os.path.getsize(fname) == 0: return True try: - with gzip.open(fname, "rb") as fh: - if fh.read() == b"": + with open(fname, "rb") as fh: + g_f = gzip.GzipFile(fileobj=fh, mode="rb") + if g_f.read() == b"": return True except gzip.BadGzipFile: pass From 0f8059e5526330f23b4e1ecae73ed84a9c6910ae Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 14 Dec 2021 11:24:10 +0100 Subject: [PATCH 251/266] Also run CI tests with Python 3.10 --- .github/workflows/pytest.yml | 2 +- CHANGELOG.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 263a4f5659..cb8b8805c1 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10] steps: - uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ddab6c4e0..23e6127341 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ * New YAML issue templates for tools bug reports and feature requests, with a much richer interface ([#1165](https://github.com/nf-core/tools/pull/1165)) * Handle synax errors in Nextflow config nicely when running `nf-core schema build` ([#1267](https://github.com/nf-core/tools/pull/1267)) * Remove base `Dockerfile` used for DSL1 pipeline container builds +* Run tests with Python 3.10 ### Modules From a20e95341b86918b9a4d115518ffdc7f8c63f733 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 14 Dec 2021 11:27:29 +0100 Subject: [PATCH 252/266] Use quotes for Python versions. Don't want to test 3.1 --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index cb8b8805c1..39aa3acae8 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9, 3.10] + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v2 From 7698bad3a8592bf7cb287fdbd312dff8343fc718 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 14 Dec 2021 11:30:55 +0100 Subject: [PATCH 253/266] Support python 3.7 --- nf_core/modules/test_yml_builder.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/nf_core/modules/test_yml_builder.py b/nf_core/modules/test_yml_builder.py index a95fcf74b7..db0b51f6e5 100644 --- a/nf_core/modules/test_yml_builder.py +++ b/nf_core/modules/test_yml_builder.py @@ -201,7 +201,15 @@ def check_if_empty_file(self, fname): g_f = gzip.GzipFile(fileobj=fh, mode="rb") if g_f.read() == b"": return True - except gzip.BadGzipFile: + except Exception as e: + # Python 3.8+ + if hasattr(gzip, "BadGzipFile"): + if isinstance(e, gzip.BadGzipFile): + pass + # Python 3.7 + else: + if isinstance(e, OSError): + pass pass return False From 94e694a4da9531047f72ac3a7f5080132c29a4dd Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 14 Dec 2021 13:37:35 +0100 Subject: [PATCH 254/266] Fix exception logic --- nf_core/modules/test_yml_builder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nf_core/modules/test_yml_builder.py b/nf_core/modules/test_yml_builder.py index db0b51f6e5..d917a81053 100644 --- a/nf_core/modules/test_yml_builder.py +++ b/nf_core/modules/test_yml_builder.py @@ -207,10 +207,10 @@ def check_if_empty_file(self, fname): if isinstance(e, gzip.BadGzipFile): pass # Python 3.7 + elif isinstance(e, OSError): + pass else: - if isinstance(e, OSError): - pass - pass + raise e return False def _md5(self, fname): From 6e0f809d547afd037b9bbd699c59875bd3d82a51 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 14 Dec 2021 13:47:29 +0100 Subject: [PATCH 255/266] Module pytest - use a bioconda package that we don't have in modules --- tests/test_modules.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_modules.py b/tests/test_modules.py index ef799e157b..b24c883485 100644 --- a/tests/test_modules.py +++ b/tests/test_modules.py @@ -22,7 +22,8 @@ def create_modules_repo_dummy(): with open(os.path.join(root_dir, "README.md"), "w") as fh: fh.writelines(["# ![nf-core/modules](docs/images/nfcore-modules_logo.png)", "\n"]) - module_create = nf_core.modules.ModuleCreate(root_dir, "star/align", "@author", "process_medium", False, False) + # bpipe is a valid package on bioconda that is very unlikely to ever be added to nf-core/modules + module_create = nf_core.modules.ModuleCreate(root_dir, "bpipe/test", "@author", "process_medium", False, False) module_create.create() return root_dir From a67bbafc4ab27a9d26429a5d532726d83597918c Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 14 Dec 2021 13:53:06 +0100 Subject: [PATCH 256/266] Update other tests execting star/align --- tests/modules/bump_versions.py | 12 ++++++------ tests/modules/create_test_yml.py | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/modules/bump_versions.py b/tests/modules/bump_versions.py index 5defa7d740..86b99e7c73 100644 --- a/tests/modules/bump_versions.py +++ b/tests/modules/bump_versions.py @@ -8,15 +8,15 @@ def test_modules_bump_versions_single_module(self): """Test updating a single module""" - # Change the star/align version to an older version - main_nf_path = os.path.join(self.nfcore_modules, "modules", "star", "align", "main.nf") + # Change the bpipe/test version to an older version + main_nf_path = os.path.join(self.nfcore_modules, "modules", "bpipe", "test", "main.nf") with open(main_nf_path, "r") as fh: content = fh.read() new_content = re.sub(r"bioconda::star=\d.\d.\d\D?", r"bioconda::star=2.6.1d", content) with open(main_nf_path, "w") as fh: fh.write(new_content) version_bumper = nf_core.modules.ModuleVersionBumper(pipeline_dir=self.nfcore_modules) - version_bumper.bump_versions(module="star/align") + version_bumper.bump_versions(module="bpipe/test") assert len(version_bumper.failed) == 0 @@ -37,13 +37,13 @@ def test_modules_bump_versions_fail(self): def test_modules_bump_versions_fail_unknown_version(self): """Fail because of an unknown version""" - # Change the star/align version to an older version - main_nf_path = os.path.join(self.nfcore_modules, "modules", "star", "align", "main.nf") + # Change the bpipe/test version to an older version + main_nf_path = os.path.join(self.nfcore_modules, "modules", "bpipe", "test", "main.nf") with open(main_nf_path, "r") as fh: content = fh.read() new_content = re.sub(r"bioconda::star=\d.\d.\d\D?", r"bioconda::star=xxx", content) with open(main_nf_path, "w") as fh: fh.write(new_content) version_bumper = nf_core.modules.ModuleVersionBumper(pipeline_dir=self.nfcore_modules) - version_bumper.bump_versions(module="star/align") + version_bumper.bump_versions(module="bpipe/test") assert "Conda package had unknown version" in version_bumper.failed[0][0] diff --git a/tests/modules/create_test_yml.py b/tests/modules/create_test_yml.py index db286181c5..3ea802b27c 100644 --- a/tests/modules/create_test_yml.py +++ b/tests/modules/create_test_yml.py @@ -41,18 +41,18 @@ def test_modules_create_test_yml_get_md5(self): def test_modules_create_test_yml_entry_points(self): """Test extracting test entry points from a main.nf file""" - meta_builder = nf_core.modules.ModulesTestYmlBuilder("star/align", False, "./", False, True) - meta_builder.module_test_main = os.path.join(self.nfcore_modules, "tests", "modules", "star", "align", "main.nf") + meta_builder = nf_core.modules.ModulesTestYmlBuilder("bpipe/test", False, "./", False, True) + meta_builder.module_test_main = os.path.join(self.nfcore_modules, "tests", "modules", "bpipe", "test", "main.nf") meta_builder.scrape_workflow_entry_points() - assert meta_builder.entry_points[0] == "test_star_align" + assert meta_builder.entry_points[0] == "test_bpipe_test" def test_modules_create_test_yml_check_inputs(self): """Test the check_inputs() function - raise UserWarning because test.yml exists""" cwd = os.getcwd() os.chdir(self.nfcore_modules) - meta_builder = nf_core.modules.ModulesTestYmlBuilder("star/align", False, "./", False, True) - meta_builder.module_test_main = os.path.join(self.nfcore_modules, "tests", "modules", "star", "align", "main.nf") + meta_builder = nf_core.modules.ModulesTestYmlBuilder("bpipe/test", False, "./", False, True) + meta_builder.module_test_main = os.path.join(self.nfcore_modules, "tests", "modules", "bpipe", "test", "main.nf") with pytest.raises(UserWarning) as excinfo: meta_builder.check_inputs() os.chdir(cwd) From 5f9774210d195fb2c759a4bdc54bcdcb9ae80531 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 14 Dec 2021 13:58:37 +0100 Subject: [PATCH 257/266] Caught one more occurence of star in tests --- tests/modules/bump_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/modules/bump_versions.py b/tests/modules/bump_versions.py index 86b99e7c73..df891cd4c7 100644 --- a/tests/modules/bump_versions.py +++ b/tests/modules/bump_versions.py @@ -41,7 +41,7 @@ def test_modules_bump_versions_fail_unknown_version(self): main_nf_path = os.path.join(self.nfcore_modules, "modules", "bpipe", "test", "main.nf") with open(main_nf_path, "r") as fh: content = fh.read() - new_content = re.sub(r"bioconda::star=\d.\d.\d\D?", r"bioconda::star=xxx", content) + new_content = re.sub(r"bioconda::bpipe=\d.\d.\d\D?", r"bioconda::bpipe=xxx", content) with open(main_nf_path, "w") as fh: fh.write(new_content) version_bumper = nf_core.modules.ModuleVersionBumper(pipeline_dir=self.nfcore_modules) From 303fb6e636b578ce83741d028d9f70e556871c21 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Tue, 14 Dec 2021 14:27:22 +0100 Subject: [PATCH 258/266] Don't run `nextflow -self-update` in CI tests Also, move static env vars to top level of the workflow. --- nf_core/pipeline-template/.github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nf_core/pipeline-template/.github/workflows/ci.yml b/nf_core/pipeline-template/.github/workflows/ci.yml index 0e8ec45ae2..d8a7240f73 100644 --- a/nf_core/pipeline-template/.github/workflows/ci.yml +++ b/nf_core/pipeline-template/.github/workflows/ci.yml @@ -8,14 +8,16 @@ on: release: types: [published] +env: + NXF_ANSI_LOG: false + CAPSULE_LOG: none + jobs: test: name: Run workflow tests # Only run on push if this is the nf-core dev branch (merged PRs) if: {% raw %}${{{% endraw %} github.event_name != 'push' || (github.event_name == 'push' && github.repository == '{{ name }}') {% raw %}}}{% endraw %} runs-on: ubuntu-latest - env: - NXF_ANSI_LOG: false strategy: matrix: # Nextflow versions @@ -32,7 +34,6 @@ jobs: - name: Install Nextflow env: - CAPSULE_LOG: none {% raw -%} NXF_VER: ${{ matrix.NXF_VER }} # Uncomment only if the edge release is more recent than the latest stable release @@ -42,7 +43,6 @@ jobs: run: | wget -qO- get.nextflow.io | bash sudo mv nextflow /usr/local/bin/ - nextflow self-update - name: Run pipeline with test data # TODO nf-core: You can customise CI pipeline run tests as required From da4150a87ea00ad2521294856b2ac8c101e8c6be Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Tue, 14 Dec 2021 14:27:15 +0000 Subject: [PATCH 259/266] Change date in CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a64c7fc101..f9520973f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # nf-core/tools: Changelog -## [v2.2 - Lead Liger](https://github.com/nf-core/tools/releases/tag/2.2) - [2021-12-09] +## [v2.2 - Lead Liger](https://github.com/nf-core/tools/releases/tag/2.2) - [2021-12-15] ### Template From 8585b248150d3e0ecd31cbcf40b8698e509bed34 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Tue, 14 Dec 2021 14:29:03 +0000 Subject: [PATCH 260/266] Change date in CHANGELOG again --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9520973f9..a4dde35113 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # nf-core/tools: Changelog -## [v2.2 - Lead Liger](https://github.com/nf-core/tools/releases/tag/2.2) - [2021-12-15] +## [v2.2 - Lead Liger](https://github.com/nf-core/tools/releases/tag/2.2) - [2021-12-14] ### Template From fed2e9b56b40c2fabaf0f166a3d05948c85209c3 Mon Sep 17 00:00:00 2001 From: ggabernet Date: Tue, 14 Dec 2021 15:58:59 +0100 Subject: [PATCH 261/266] fix gh wf workflow --- .github/workflows/create-lint-wf.yml | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/create-lint-wf.yml b/.github/workflows/create-lint-wf.yml index d7cff18e29..d714477651 100644 --- a/.github/workflows/create-lint-wf.yml +++ b/.github/workflows/create-lint-wf.yml @@ -1,6 +1,10 @@ name: Create a pipeline and lint it on: [push, pull_request] +env: + NXF_ANSI_LOG: false + CAPSULE_LOG: none + jobs: MakeTestWorkflow: runs-on: ubuntu-latest @@ -8,8 +12,14 @@ jobs: NXF_ANSI_LOG: false strategy: matrix: - # Nextflow versions: check pipeline minimum and latest edge version - nxf_ver: ["NXF_VER=21.10.3", "NXF_EDGE=1"] + # 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: - uses: actions/checkout@v2 name: Check out source-code repository @@ -26,12 +36,13 @@ jobs: - 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/ - export ${{ matrix.nxf_ver }} - nextflow self-update - name: nf-core create run: nf-core --log-file log.txt create -n testpipeline -d "This pipeline is for testing" -a "Testing McTestface" From f51c46299e912ed17d6937c75d349374485508ed Mon Sep 17 00:00:00 2001 From: ggabernet Date: Tue, 14 Dec 2021 16:00:30 +0100 Subject: [PATCH 262/266] fix test wf --- .github/workflows/create-test-wf.yml | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/create-test-wf.yml b/.github/workflows/create-test-wf.yml index 4aa24e0894..9b13cadd39 100644 --- a/.github/workflows/create-test-wf.yml +++ b/.github/workflows/create-test-wf.yml @@ -1,6 +1,10 @@ name: Create a pipeline and test it on: [push, pull_request] +env: + NXF_ANSI_LOG: false + CAPSULE_LOG: none + jobs: RunTestWorkflow: runs-on: ubuntu-latest @@ -8,10 +12,14 @@ jobs: NXF_ANSI_LOG: false strategy: matrix: - # Nextflow versions: check pipeline minimum and latest edge version - nxf_ver: - - "NXF_VER=21.10.3" - # - "NXF_EDGE=1" + # 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: - uses: actions/checkout@v2 name: Check out source-code repository @@ -28,12 +36,13 @@ jobs: - 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/ - export ${{ matrix.nxf_ver }} - nextflow self-update - name: Run nf-core/tools run: | From 7719f3c614c305e54ede36eab357f8af39776fcf Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Tue, 14 Dec 2021 15:01:12 +0000 Subject: [PATCH 263/266] Update nf_core/pipeline-template/workflows/pipeline.nf Co-authored-by: Maxime U. Garcia --- nf_core/pipeline-template/workflows/pipeline.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/pipeline-template/workflows/pipeline.nf b/nf_core/pipeline-template/workflows/pipeline.nf index 7ac029844f..460a3cf20a 100644 --- a/nf_core/pipeline-template/workflows/pipeline.nf +++ b/nf_core/pipeline-template/workflows/pipeline.nf @@ -46,8 +46,8 @@ include { INPUT_CHECK } from '../subworkflows/local/input_check' // // MODULE: Installed directly from nf-core/modules // -include { FASTQC } from '../modules/nf-core/modules/fastqc/main' -include { MULTIQC } from '../modules/nf-core/modules/multiqc/main' +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' /* From cc54b9e7cccaec61acb99f35bf97e1722cc00f83 Mon Sep 17 00:00:00 2001 From: ggabernet Date: Tue, 14 Dec 2021 16:03:25 +0100 Subject: [PATCH 264/266] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4dde35113..93ec4fa4a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ * Erase temporary files and folders while performing Python tests (pytest) * Remove base `Dockerfile` used for DSL1 pipeline container builds * Run tests with Python 3.10 +* [#1363](https://github.com/nf-core/tools/pull/1363) Fix tools CI workflow nextflow versions. ### Modules From 5f8fad6789826869466ab3827cd8339915ead94f Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Tue, 14 Dec 2021 15:34:59 +0000 Subject: [PATCH 265/266] Update nf_core/pipeline-template/lib/NfcoreTemplate.groovy Co-authored-by: Jose Espinosa-Carrasco --- nf_core/pipeline-template/lib/NfcoreTemplate.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/pipeline-template/lib/NfcoreTemplate.groovy b/nf_core/pipeline-template/lib/NfcoreTemplate.groovy index 06499409c5..2fc0a9b9b6 100755 --- a/nf_core/pipeline-template/lib/NfcoreTemplate.groovy +++ b/nf_core/pipeline-template/lib/NfcoreTemplate.groovy @@ -24,7 +24,7 @@ class NfcoreTemplate { 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 enviroment but can be acheived via one or more of the following:\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" + From a5dc3c85c5fe6a9f21792396d0f6142dcc2a4a52 Mon Sep 17 00:00:00 2001 From: Harshil Patel Date: Tue, 14 Dec 2021 15:57:34 +0000 Subject: [PATCH 266/266] Warn instead of fail if module is different from remote --- nf_core/modules/lint/module_changes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/modules/lint/module_changes.py b/nf_core/modules/lint/module_changes.py index f36035e2cd..44c2501424 100644 --- a/nf_core/modules/lint/module_changes.py +++ b/nf_core/modules/lint/module_changes.py @@ -49,7 +49,7 @@ def module_changes(module_lint_object, module): remote_copy = r.content.decode("utf-8") if local_copy != remote_copy: - module.failed.append( + module.warned.append( ( "check_local_copy", "Local copy of module does not match remote",