Skip to content

Commit

Permalink
Merge pull request #3220 from mashehu/fix-bump-version
Browse files Browse the repository at this point in the history
Include .nf-core.yml in `nf-core pipelines bump-version`
  • Loading branch information
mashehu authored Oct 15, 2024
2 parents ecddc61 + 30e3d5d commit b834c0f
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 55 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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:
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)

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

0 comments on commit b834c0f

Please sign in to comment.