From 63de39b20aa7978e1cf22730d95c8ccf1bc1c1e1 Mon Sep 17 00:00:00 2001 From: Amethyst Reese Date: Sat, 26 Oct 2024 00:14:19 -0700 Subject: [PATCH] Add new `dynamic_version` option --- attribution/__version__.py | 25 +++++++++++++++++++++++ attribution/generate.py | 39 ++++++++++++++++++++++++++++++++++++ attribution/main.py | 19 ++++++++++++++++-- attribution/project.py | 1 + attribution/tests/project.py | 6 ++++++ docs/guide.rst | 16 +++++++++++++++ pyproject.toml | 1 + 7 files changed, 105 insertions(+), 2 deletions(-) diff --git a/attribution/__version__.py b/attribution/__version__.py index e36f7ab..2012fae 100644 --- a/attribution/__version__.py +++ b/attribution/__version__.py @@ -5,3 +5,28 @@ """ __version__ = "1.8.0" + +try: + import re + import subprocess + from pathlib import Path + + version_suffix = "+dev" + path = Path(__file__).resolve().parent + while path != path.parent: + if (path / ".git").is_dir(): + proc = subprocess.run( + ("git", "describe"), text=True, capture_output=True, check=True + ) + if match := re.search(r"-(\d+)-(g[a-f0-9]+)$", proc.stdout): + count, ref = match.groups() + version_suffix = f"+dev{count}-{ref}" + break + + path = path.parent + +except Exception as e: + print(f"version suffix failed: {e}") + +finally: + __version__ += version_suffix diff --git a/attribution/generate.py b/attribution/generate.py index e2d2a7f..f62d3ab 100644 --- a/attribution/generate.py +++ b/attribution/generate.py @@ -103,6 +103,45 @@ class VersionFile(GeneratedFile): ''' +class DynamicVersionFile(GeneratedFile): + FILENAME = "{project.package}/__version__.py" + TEMPLATE = '''\ + """ + This file is automatically generated by attribution. + + Do not edit manually. Get more info at https://attribution.omnilib.dev + """ + + __version__ = "{{ project.latest.version }}" + + try: + import re + import subprocess + from pathlib import Path + + version_suffix = "+dev" + path = Path(__file__).resolve().parent + while path != path.parent: + if (path / ".git").is_dir(): + proc = subprocess.run( + ("git", "describe"), text=True, capture_output=True, check=True + ) + if match := re.search(r"-(\\d+)-(g[a-f0-9]+)$", proc.stdout): + count, ref = match.groups() + version_suffix = f"+dev{count}-{ref}" + break + + path = path.parent + + except Exception as e: + print(f"version suffix failed: {e}") + + finally: + __version__ += version_suffix + + ''' + + class CargoFile(GeneratedFile): EXPECTS = ("package_name", "package_dir") FILENAME = "{package_dir}/Cargo.toml" diff --git a/attribution/main.py b/attribution/main.py index fb7458a..78eb2bc 100644 --- a/attribution/main.py +++ b/attribution/main.py @@ -10,7 +10,7 @@ import tomlkit from attribution import __version__ -from .generate import CargoFile, Changelog, NpmFile, VersionFile +from .generate import CargoFile, Changelog, DynamicVersionFile, NpmFile, VersionFile from .helpers import sh from .project import Project from .tag import Tag @@ -37,6 +37,12 @@ def init() -> None: version_file = click.confirm( "Use __version__.py file", default=project.config["version_file"] ) + dynamic_version = False + if version_file: + dynamic_version = click.confirm( + "Use dynamic version suffix (eg. '1.0+git4-gabc123')", + default=project.config["dynamic_version"], + ) signed_tags = click.confirm( "Use GPG signed tags", default=project.config["signed_tags"] ) @@ -66,13 +72,17 @@ def init() -> None: table["package"] = package table["signed_tags"] = signed_tags table["version_file"] = version_file + table["dynamic_version"] = dynamic_version project.pyproject_path().write_text(tomlkit.dumps(pyproject)) # pick up any changes project = Project.load() if version_file: - VersionFile(project).write() + if dynamic_version: + DynamicVersionFile(project).write() + else: + VersionFile(project).write() @main.command("debug") @@ -186,6 +196,11 @@ def tag_release(version: Version, message: Optional[str]) -> None: sh("git commit --amend --no-edit") tag.update(message=message, signed=project.config["signed_tags"]) + if project.config.get("dynamic_version"): + path = DynamicVersionFile(project).write() + sh(f"git add {path}") + sh("git commit -m 'Dynamic version file'") + except Exception: mfile = Path(f".attribution-{version}.txt").resolve() mfile.write_text(message) diff --git a/attribution/project.py b/attribution/project.py index 2b47f08..1b47517 100644 --- a/attribution/project.py +++ b/attribution/project.py @@ -102,6 +102,7 @@ def load(cls, path: Optional[Path] = None) -> "Project": "ignored_authors": [], "version_file": True, "signed_tags": True, + "dynamic_version": False, } if cls.pyproject_path(path).is_file(): diff --git a/attribution/tests/project.py b/attribution/tests/project.py index b384d2e..8b58397 100644 --- a/attribution/tests/project.py +++ b/attribution/tests/project.py @@ -164,6 +164,7 @@ def test_load(self, cwd_mock): "cargo_packages": [], "ignored_authors": [], "version_file": True, + "dynamic_version": False, "signed_tags": True, }, ) @@ -181,6 +182,7 @@ def test_load(self, cwd_mock): "cargo_packages": [], "ignored_authors": [], "version_file": True, + "dynamic_version": False, "signed_tags": True, }, ) @@ -197,6 +199,7 @@ def test_load(self, cwd_mock): "cargo_packages": [], "ignored_authors": [], "version_file": True, + "dynamic_version": False, "signed_tags": True, }, ) @@ -213,6 +216,7 @@ def test_load(self, cwd_mock): "cargo_packages": [], "ignored_authors": [], "version_file": False, + "dynamic_version": False, "signed_tags": True, }, ) @@ -228,6 +232,7 @@ def test_load(self, cwd_mock): "cargo_packages": [], "ignored_authors": [], "version_file": True, + "dynamic_version": False, "signed_tags": True, }, ) @@ -243,6 +248,7 @@ def test_load(self, cwd_mock): "cargo_packages": [], "ignored_authors": [], "version_file": True, + "dynamic_version": False, "signed_tags": True, }, ) diff --git a/docs/guide.rst b/docs/guide.rst index 4fe246f..ef1d59d 100644 --- a/docs/guide.rst +++ b/docs/guide.rst @@ -171,6 +171,22 @@ Options available are described as follows: to not have a managed ``__version__.py`` file, this value should be set to ``false``. +.. attribute:: dynamic_version + :type: bool + :value: False + + Enables generating a dynamic ``__version__.py`` that generates version + suffixes based on the local repository's revision relative to the last + release. When a release is tagged, a static version file is committed, + without the dynamic suffix logic, before tagging the version bump, and + then the dynamic version file is reinstated after the release is tagged. + + If the project's git revision can be queried, then the dynamic version + string will look like ``1.8.0+dev6-g3c61fc2``. Otherwise, the version + string will fall back to the generic form ``1.8.0+dev``. + + Requires enabling :attr:`version_file`. + Alternative Packaging ^^^^^^^^^^^^^^^^^^^^^ diff --git a/pyproject.toml b/pyproject.toml index d2c43e2..73e8888 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,6 +67,7 @@ ignored_authors = ["dependabot[bot]", "pyup.io bot"] version_file = true signed_tags = true cargo_packages = ["fake_crate"] +dynamic_version = true [tool.coverage.run] branch = true