From 71f56a1720051561607fc7568c2bccfab9a92a70 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 16 Feb 2025 19:10:05 -0700 Subject: [PATCH 1/4] test(declaration): add unit tests of user-defined version stamping patterns --- .../declarations/test_pattern_declaration.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/unit/semantic_release/version/declarations/test_pattern_declaration.py b/tests/unit/semantic_release/version/declarations/test_pattern_declaration.py index b49f87fa0..fd7cb7dad 100644 --- a/tests/unit/semantic_release/version/declarations/test_pattern_declaration.py +++ b/tests/unit/semantic_release/version/declarations/test_pattern_declaration.py @@ -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", @@ -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. """ From bd6c1e5f0e4402d61871d7cf064b06d26cf77649 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 16 Feb 2025 19:07:13 -0700 Subject: [PATCH 2/4] test(cmd-version): add test to demonstrate github actions yaml version tag stamping --- tests/e2e/cmd_version/test_version_stamp.py | 72 +++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/e2e/cmd_version/test_version_stamp.py b/tests/e2e/cmd_version/test_version_stamp.py index c63d5b66c..9d45b6019 100644 --- a/tests/e2e/cmd_version/test_version_stamp.py +++ b/tests/e2e/cmd_version/test_version_stamp.py @@ -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, From 85bffa9cbd4d00cb338d73070886fe76e80433e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benedikt=20He=C3=9F?= Date: Tue, 11 Feb 2025 17:06:24 +0100 Subject: [PATCH 3/4] feat(cmd-version): extend `version_variables` to stamp versions with `@` symbol separator Resolves: #1156 --- src/semantic_release/version/declaration.py | 11 ++--------- src/semantic_release/version/declarations/pattern.py | 6 +++--- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/semantic_release/version/declaration.py b/src/semantic_release/version/declaration.py index 92da001a1..3c225d1b5 100644 --- a/src/semantic_release/version/declaration.py +++ b/src/semantic_release/version/declaration.py @@ -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 @@ -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 diff --git a/src/semantic_release/version/declarations/pattern.py b/src/semantic_release/version/declarations/pattern.py index 73f67b465..55873ce0a 100644 --- a/src/semantic_release/version/declarations/pattern.py +++ b/src/semantic_release/version/declarations/pattern.py @@ -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['"])?(?['"])?{value_replace_pattern_str}(?P=quote2)?""", ], From 8f5c871cba5918f976f9e5975010ed31e27183cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benedikt=20He=C3=9F?= Date: Tue, 11 Feb 2025 17:07:23 +0100 Subject: [PATCH 4/4] docs(configuration): clarify `version_variables` config description & `@` separator usage --- docs/configuration.rst | 61 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 7fc4838c4..a94591ff1 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -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 @@ -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