Skip to content

config, version_variables: allow @ as separator and v to precede the version #1185

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

Merged
merged 4 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 55 additions & 6 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1340,14 +1340,22 @@ to substitute the version number in the file. The replacement algorithm is **ONL
pattern match and replace. It will **NOT** evaluate the code nor will PSR understand
any internal object structures (ie. ``file:object.version`` will not work).

.. important::
The Regular Expression expects a version value to exist in the file to be replaced.
It cannot be an empty string or a non-semver compliant string. If this is the very
first time you are using PSR, we recommend you set the version to ``0.0.0``.
The regular expression generated from the ``version_variables`` definition will:

This may become more flexible in the future with resolution of issue `#941`_.
1. Look for the specified ``variable`` name in the ``file``. The variable name can be
enclosed by single (``'``) or double (``"``) quotation marks but they must match.

.. _#941: https://github.com/python-semantic-release/python-semantic-release/issues/941
2. The variable name defined by ``variable`` and the version must be separated by
an operand symbol (``=``, ``:``, ``:=``, or ``@``). Whitespace is optional around
the symbol.

3. The value of the variable must match a `SemVer`_ regular expression and can be
enclosed by single (``'``) or double (``"``) quotation marks but they must match. However,
the enclosing quotes of the value do not have to match the quotes surrounding the variable
name.

4. If the format type is set to ``tf`` then the variable value must have the matching prefix
and suffix of the :ref:`config-tag_format` setting around the `SemVer`_ version number.

Given the pattern matching nature of this feature, the Regular Expression is able to
support most file formats because of the similarity of variable declaration across
Expand All @@ -1360,6 +1368,47 @@ regardless of file extension because it looks for a matching pattern string.
TOML files as it actually will interpret the TOML file and replace the version
number before writing the file back to disk.

This is a comprehensive list (but not all variations) of examples where the following versions
will be matched and replaced by the new version:

.. code-block::

# Common variable declaration formats
version='1.2.3'
version = "1.2.3"
release = "v1.2.3" # if tag_format is set

# YAML
version: 1.2.3

# JSON
"version": "1.2.3"

# NPM & GitHub Actions YAML
version@1.2.3
version@v1.2.3 # if tag_format is set

# Walrus Operator
version := "1.2.3"

# Excessive whitespace
version = '1.2.3'

# Mixed Quotes
"version" = '1.2.3'

# Custom Tag Format with tag_format set (monorepos)
__release__ = "module-v1.2.3"

.. important::
The Regular Expression expects a version value to exist in the file to be replaced.
It cannot be an empty string or a non-semver compliant string. If this is the very
first time you are using PSR, we recommend you set the version to ``0.0.0``.

This may become more flexible in the future with resolution of issue `#941`_.

.. _#941: https://github.com/python-semantic-release/python-semantic-release/issues/941

.. warning::
If the file (ex. JSON) you are replacing has two of the same variable name in it,
this pattern match will not be able to differentiate between the two and will replace
Expand Down
11 changes: 2 additions & 9 deletions src/semantic_release/version/declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
from semantic_release.version.declarations.toml import TomlVersionDeclaration

if TYPE_CHECKING: # pragma: no cover
from typing import Any

from semantic_release.version.version import Version


Expand Down Expand Up @@ -66,13 +64,8 @@ def content(self) -> str:
self._content = self.path.read_text()
return self._content

# mypy doesn't like properties?
@content.setter # type: ignore[attr-defined]
def _(self, _: Any) -> None:
raise AttributeError("'content' cannot be set directly")

@content.deleter # type: ignore[attr-defined]
def _(self) -> None:
@content.deleter
def content(self) -> None:
log.debug("resetting instance-stored source file contents")
self._content = None

Expand Down
6 changes: 3 additions & 3 deletions src/semantic_release/version/declarations/pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,9 @@ def from_string_definition(
# Supports optional matching quotations around variable name
# Negative lookbehind to ensure we don't match part of a variable name
f"""(?x)(?P<quote1>['"])?(?<![\\w.-]){regex_escape(variable)}(?P=quote1)?""",
# Supports walrus, equals sign, or colon as assignment operator ignoring
# whitespace separation
r"\s*(:=|[:=])\s*",
# Supports walrus, equals sign, colon, or @ as assignment operator
# ignoring whitespace separation
r"\s*(:=|[:=@])\s*",
# Supports optional matching quotations around a version pattern (tag or raw format)
f"""(?P<quote2>['"])?{value_replace_pattern_str}(?P=quote2)?""",
],
Expand Down
72 changes: 72 additions & 0 deletions tests/e2e/cmd_version/test_version_stamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,78 @@ def test_stamp_version_variables_json(
assert orig_json == resulting_json_obj


@pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__)
def test_stamp_version_variables_yaml_github_actions(
cli_runner: CliRunner,
update_pyproject_toml: UpdatePyprojectTomlFn,
default_tag_format_str: str,
) -> None:
"""
Given a yaml file with github actions 'uses:' directives which use @vX.Y.Z version declarations,
When a version is stamped and configured to stamp the version using the tag format,
Then the file is updated with the new version in the tag format

Based on https://github.com/python-semantic-release/python-semantic-release/issues/1156
"""
orig_version = "0.0.0"
new_version = "0.1.0"
target_file = Path("combined.yml")
action1_yaml_filepath = "my-org/my-actions/.github/workflows/action1.yml"
action2_yaml_filepath = "my-org/my-actions/.github/workflows/action2.yml"
orig_yaml = dedent(
f"""\
---
on:
workflow_call:

jobs:
action1:
uses: {action1_yaml_filepath}@{default_tag_format_str.format(version=orig_version)}
action2:
uses: {action2_yaml_filepath}@{default_tag_format_str.format(version=orig_version)}
"""
)
expected_action1_value = (
f"{action1_yaml_filepath}@{default_tag_format_str.format(version=new_version)}"
)
expected_action2_value = (
f"{action2_yaml_filepath}@{default_tag_format_str.format(version=new_version)}"
)

# Setup: Write initial text in file
target_file.write_text(orig_yaml)

# Setup: Set configuration to modify the yaml file
update_pyproject_toml(
"tool.semantic_release.version_variables",
[
f"{target_file}:{action1_yaml_filepath}:{VersionStampType.TAG_FORMAT.value}",
f"{target_file}:{action2_yaml_filepath}:{VersionStampType.TAG_FORMAT.value}",
],
)

# Act
cli_cmd = VERSION_STAMP_CMD
result = cli_runner.invoke(main, cli_cmd[1:])

# Check the result
assert_successful_exit_code(result, cli_cmd)

# Read content
resulting_yaml_obj = yaml.safe_load(target_file.read_text())

# Check the version was updated
assert expected_action1_value == resulting_yaml_obj["jobs"]["action1"]["uses"]
assert expected_action2_value == resulting_yaml_obj["jobs"]["action2"]["uses"]

# Check the rest of the content is the same (by setting the version & comparing)
original_yaml_obj = yaml.safe_load(orig_yaml)
original_yaml_obj["jobs"]["action1"]["uses"] = expected_action1_value
original_yaml_obj["jobs"]["action2"]["uses"] = expected_action2_value

assert original_yaml_obj == resulting_yaml_obj


@pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__)
def test_stamp_version_variables_yaml_kustomization_container_spec(
cli_runner: CliRunner,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,24 @@ def test_pattern_declaration_is_version_replacer():
'''__version__ = "module-v1.0.0"''',
f'''__version__ = "module-v{next_version}"''',
),
(
# Based on https://github.com/python-semantic-release/python-semantic-release/issues/1156
"Using default tag format for github actions uses-directive",
f"{test_file}:repo/action-name:{VersionStampType.TAG_FORMAT.value}",
lazy_fixture(default_tag_format_str.__name__),
# Uses @ symbol separator without quotes or spaces
""" uses: repo/action-name@v1.0.0""",
f""" uses: repo/action-name@v{next_version}""",
),
(
# Based on https://github.com/python-semantic-release/python-semantic-release/issues/1156
"Using custom tag format for github actions uses-directive",
f"{test_file}:repo/action-name:{VersionStampType.TAG_FORMAT.value}",
"module-v{version}",
# Uses @ symbol separator without quotes or spaces
""" uses: repo/action-name@module-v1.0.0""",
f""" uses: repo/action-name@module-v{next_version}""",
),
(
# Based on https://github.com/python-semantic-release/python-semantic-release/issues/846
"Using default tag format for multi-line yaml",
Expand Down Expand Up @@ -205,7 +223,7 @@ def test_pattern_declaration_from_definition(
When update_file_w_version() is called with a new version,
Then the file is updated with the new version string in the specified tag or number format

Version variables can be separated by either "=", ":", or ':=' with optional whitespace
Version variables can be separated by either "=", ":", "@", or ':=' with optional whitespace
between operator and variable name. The variable name or values can also be wrapped in either
single or double quotes.
"""
Expand Down
Loading