Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include .nf-core.yml in nf-core pipelines bump-version #3220

Merged
merged 13 commits into from
Oct 15, 2024
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

### General

- Include .nf-core.yml in `nf-core pipelines bump-version` ([#3220](https://github.com/nf-core/tools/pull/3220))

## [v3.0.2 - Titanium Tapir Patch](https://github.com/nf-core/tools/releases/tag/3.0.2) - [2024-10-11]

### Template
Expand Down
182 changes: 130 additions & 52 deletions nf_core/pipelines/bump_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import logging
import re
from pathlib import Path
from typing import List, Tuple, Union
from typing import List, Optional, Tuple, Union

import rich.console
from ruamel.yaml import YAML

import nf_core.utils
from nf_core.utils import Pipeline
Expand Down Expand Up @@ -60,6 +61,7 @@ def bump_pipeline_version(pipeline_obj: Pipeline, new_version: str) -> None:
f"/releases/tag/{new_version}",
)
],
yaml_key=["report_comment"],
)
if multiqc_current_version != "dev" and multiqc_new_version == "dev":
update_file_version(
Expand All @@ -71,6 +73,7 @@ def bump_pipeline_version(pipeline_obj: Pipeline, new_version: str) -> None:
"/tree/dev",
)
],
yaml_key=["report_comment"],
)
if multiqc_current_version == "dev" and multiqc_new_version != "dev":
update_file_version(
Expand All @@ -82,6 +85,7 @@ def bump_pipeline_version(pipeline_obj: Pipeline, new_version: str) -> None:
f"/releases/tag/{multiqc_new_version}",
)
],
yaml_key=["report_comment"],
)
update_file_version(
Path("assets", "multiqc_config.yml"),
Expand All @@ -92,6 +96,7 @@ def bump_pipeline_version(pipeline_obj: Pipeline, new_version: str) -> None:
f"/{multiqc_new_version}/",
),
],
yaml_key=["report_comment"],
)
# nf-test snap files
pipeline_name = pipeline_obj.nf_config.get("manifest.name", "").strip(" '\"")
Expand All @@ -107,6 +112,20 @@ def bump_pipeline_version(pipeline_obj: Pipeline, new_version: str) -> None:
)
],
)
# .nf-core.yml - pipeline version
# update entry: version: 1.0.0dev, but not `nf_core_version`, or `bump_version`
update_file_version(
".nf-core.yml",
pipeline_obj,
[
(
current_version,
new_version,
)
],
required=False,
yaml_key=["template", "version"],
)


def bump_nextflow_version(pipeline_obj: Pipeline, new_version: str) -> None:
Expand Down Expand Up @@ -147,10 +166,11 @@ def bump_nextflow_version(pipeline_obj: Pipeline, new_version: str) -> None:
# example:
# NXF_VER:
# - "20.04.0"
rf"- \"{re.escape(current_version)}\"",
f'- "{new_version}"',
current_version,
new_version,
)
],
yaml_key=["jobs", "test", "strategy", "matrix", "NXF_VER"],
)

# README.md - Nextflow version badge
Expand All @@ -161,70 +181,128 @@ def bump_nextflow_version(pipeline_obj: Pipeline, new_version: str) -> None:
(
rf"nextflow%20DSL2-%E2%89%A5{re.escape(current_version)}-23aa62.svg",
f"nextflow%20DSL2-%E2%89%A5{new_version}-23aa62.svg",
),
(
# example: 1. Install [`Nextflow`](https://www.nextflow.io/docs/latest/getstarted.html#installation) (`>=20.04.0`)
rf"1\.\s*Install\s*\[`Nextflow`\]\(https:\/\/www\.nextflow\.io\/docs\/latest\/getstarted\.html#installation\)\s*\(`>={re.escape(current_version)}`\)",
f"1. Install [`Nextflow`](https://www.nextflow.io/docs/latest/getstarted.html#installation) (`>={new_version}`)",
),
)
],
)


def update_file_version(filename: Union[str, Path], pipeline_obj: Pipeline, patterns: List[Tuple[str, str]]) -> None:
"""Updates the version number in a requested file.
def update_file_version(
filename: Union[str, Path],
pipeline_obj: Pipeline,
patterns: List[Tuple[str, str]],
required: bool = True,
yaml_key: Optional[List[str]] = None,
) -> None:
"""
Updates a file with a new version number.

Args:
mashehu marked this conversation as resolved.
Show resolved Hide resolved
filename (str): File to scan.
pipeline_obj (nf_core.pipelines.lint.PipelineLint): A PipelineLint object that holds information
about the pipeline contents and build files.
pattern (str): Regex pattern to apply.

Raises:
ValueError, if the version number cannot be found.
filename (str): The name of the file to update.
pipeline_obj (nf_core.utils.Pipeline): A `Pipeline` object that holds information
about the pipeline contents.
patterns (List[Tuple[str, str]]): A list of tuples containing the regex patterns to
match and the replacement strings.
required (bool, optional): Whether the file is required to exist. Defaults to `True`.
yaml_key (Optional[List[str]], optional): The YAML key to update. Defaults to `None`.
"""
# Load the file
fn = pipeline_obj._fp(filename)
content = ""
try:
with open(fn) as fh:
content = fh.read()
except FileNotFoundError:
fn: Path = pipeline_obj._fp(filename)

if not fn.exists():
log.warning(f"File not found: '{fn}'")
return

replacements = []
for pattern in patterns:
found_match = False
if yaml_key:
update_yaml_file(fn, patterns, yaml_key, required)
else:
update_text_file(fn, patterns, required)
mashehu marked this conversation as resolved.
Show resolved Hide resolved

newcontent = []
for line in content.splitlines():
# Match the pattern
matches_pattern = re.findall(rf"^.*{pattern[0]}.*$", line)
if matches_pattern:
found_match = True

# Replace the match
newline = re.sub(pattern[0], pattern[1], line)
newcontent.append(newline)
def update_yaml_file(fn: Path, patterns: List[Tuple[str, str]], yaml_key: List[str], required: bool):
"""
Updates a YAML file with a new version number.

# Save for logging
replacements.append((line, newline))
Args:
fn (Path): The name of the file to update.
patterns (List[Tuple[str, str]]): A list of tuples containing the regex patterns to
match and the replacement strings.
yaml_key (List[str]): The YAML key to update.
required (bool): Whether the file is required to exist.
"""
yaml = YAML()
yaml.preserve_quotes = True
with open(fn) as file:
yaml_content = yaml.load(file)

try:
target = yaml_content
for key in yaml_key[:-1]:
target = target[key]

# No match, keep line as it is
last_key = yaml_key[-1]
current_value = target[last_key]

new_value = current_value
for pattern, replacement in patterns:
# check if current value is list
if isinstance(current_value, list):
new_value = [re.sub(pattern, replacement, item) for item in current_value]
else:
newcontent.append(line)
new_value = re.sub(pattern, replacement, current_value)

if found_match:
content = "\n".join(newcontent) + "\n"
else:
log.error(f"Could not find version number in {filename}: `{pattern}`")
if new_value != current_value:
target[last_key] = new_value
with open(fn, "w") as file:
yaml.dump(yaml_content, file)
log.info(f"Updated version in YAML file '{fn}'")
log_change(str(current_value), str(new_value))
except KeyError as e:
handle_error(f"Could not find key {e} in the YAML structure of {fn}", required)

log.info(f"Updated version in '{filename}'")
for replacement in replacements:
stderr.print(f" [red] - {replacement[0].strip()}", highlight=False)
stderr.print(f" [green] + {replacement[1].strip()}", highlight=False)
stderr.print("\n")

with open(fn, "w") as fh:
fh.write(content)
def update_text_file(fn: Path, patterns: List[Tuple[str, str]], required: bool):
"""
Updates a text file with a new version number.

Args:
fn (Path): The name of the file to update.
patterns (List[Tuple[str, str]]): A list of tuples containing the regex patterns to
match and the replacement strings.
required (bool): Whether the file is required to exist.
"""
with open(fn) as file:
content = file.read()

updated = False
for pattern, replacement in patterns:
new_content, count = re.subn(pattern, replacement, content)
if count > 0:
log_change(content, new_content)
content = new_content
updated = True
log.info(f"Updated version in '{fn}'")
log.debug(f"Replaced pattern '{pattern}' with '{replacement}' {count} times")
elif required:
handle_error(f"Could not find version number in {fn}: `{pattern}`", required)

if updated:
with open(fn, "w") as file:
file.write(content)


def handle_error(message: str, required: bool):
if required:
raise ValueError(message)
else:
log.info(message)


def log_change(old_content: str, new_content: str):
old_lines = old_content.splitlines()
new_lines = new_content.splitlines()

for old_line, new_line in zip(old_lines, new_lines):
if old_line != new_line:
stderr.print(f" [red] - {old_line.strip()}", highlight=False)
stderr.print(f" [green] + {new_line.strip()}", highlight=False)

stderr.print("\n")
19 changes: 16 additions & 3 deletions tests/pipelines/test_bump_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,25 @@ def test_bump_pipeline_version(self):
"""Test that making a release with the working example files works"""

# Bump the version number
nf_core.pipelines.bump_version.bump_pipeline_version(self.pipeline_obj, "1.1")
nf_core.pipelines.bump_version.bump_pipeline_version(self.pipeline_obj, "1.1.0")
new_pipeline_obj = nf_core.utils.Pipeline(self.pipeline_dir)

# Check nextflow.config
new_pipeline_obj.load_pipeline_config()
assert new_pipeline_obj.nf_config["manifest.version"].strip("'\"") == "1.1"
assert new_pipeline_obj.nf_config["manifest.version"].strip("'\"") == "1.1.0"

# Check multiqc_config.yml
with open(new_pipeline_obj._fp("assets/multiqc_config.yml")) as fh:
multiqc_config = yaml.safe_load(fh)

assert "report_comment" in multiqc_config
assert "/releases/tag/1.1.0" in multiqc_config["report_comment"]

# Check .nf-core.yml
with open(new_pipeline_obj._fp(".nf-core.yml")) as fh:
nf_core_yml = yaml.safe_load(fh)
if nf_core_yml["template"]:
assert nf_core_yml["template"]["version"] == "1.1.0"

def test_dev_bump_pipeline_version(self):
"""Test that making a release works with a dev name and a leading v"""
Expand All @@ -33,7 +46,7 @@ def test_dev_bump_pipeline_version(self):
def test_bump_nextflow_version(self):
# Bump the version number to a specific version, preferably one
# we're not already on
version = "22.04.3"
version = "25.04.2"
nf_core.pipelines.bump_version.bump_nextflow_version(self.pipeline_obj, version)
new_pipeline_obj = nf_core.utils.Pipeline(self.pipeline_dir)
new_pipeline_obj._load()
Expand Down
Loading