from __future__ import annotations import shlex import subprocess import re import sys import os from setup import ROOT_DIR from scikit_build_core.settings.skbuild_read_settings import rich_print as _rich_print from shutil import which from typing import Optional def rich_print(*args): if sys.platform.startswith("win"): # Workaround for: # KeyError: 'WinError' # File "[...]\scikit_build_core\_logging.py", line 122, in # lambda m: "".join(colors()[x] for x in m.group(1).split()), return print(*args) return _rich_print(*args) class Versioning: COMMAND = '{git} describe --tags --long --dirty' GIT_BRANCH = '{git} rev-parse --abbrev-ref HEAD' IS_TAGGED_CMD = '{git} tag --list --points-at=HEAD' FMT_DEV = '{tag}.dev0' FMT_TAGGED = '{tag}' CMAKE_VERSION_R = r"set\(LIEF_VERSION_(MAJOR|MINOR|PATCH)\s(\d+)\)" def __init__(self): self._git = which("git") def _exec_git_cmd(self, cmd: str, **kwargs) -> str: args = dict(kwargs) args["git"] = self._git cmd_list = shlex.split(cmd.format(**args)) return subprocess.check_output(cmd_list, text=True, cwd=ROOT_DIR.as_posix()).strip() def has_git(self) -> bool: return self._git is not None and (ROOT_DIR / ".git").is_dir() def get_branch(self) -> Optional[str]: if not self.has_git(): return None try: return self._exec_git_cmd(Versioning.GIT_BRANCH) except subprocess.SubprocessError as e: rich_print(f"[red]Error: {e}") return None def format_version(self, version: str, fmt: str, is_dev: bool = False): branch = self.get_branch() if branch is not None and branch.startswith("release-"): _, version = branch.split("release-") return version if branch is not None and branch.startswith("release/"): _, version = branch.split("release/") major, minor, patch = version.split('.') if patch == 'x': return f"{major}.{minor}" return version parts = version.split('-') assert len(parts) in (3, 4) dirty = len(parts) == 4 tag, count, sha = parts[:3] MA, MI, PA = map(int, tag.split(".")) # 0.9.0 -> (0, 9, 0) if is_dev: if MI == 17: # Special case for the 0.17.0 which is the last version of # the 0.X.Y series tag = "1.0.0" else: tag = f"{MA}.{MI + 1}.{0}" if count == '0' and not dirty: return tag return fmt.format(tag=tag, gitsha=sha.lstrip('g')) def version_from_git(self) -> Optional[str]: if not self.has_git(): return None try: is_tagged = self._exec_git_cmd(Versioning.IS_TAGGED_CMD) != "" git_version = self._exec_git_cmd(Versioning.COMMAND) if is_tagged: return self.format_version(version=git_version, fmt=Versioning.FMT_TAGGED) return self.format_version(version=git_version, fmt=Versioning.FMT_DEV, is_dev=True) except Exception as e: rich_print(f"[red]Error: {e}") return None def version_from_cmake(self) -> Optional[str]: main_cmake = ROOT_DIR / "CMakeLists.txt" cmakefile = main_cmake.read_text() major: Optional[int] = None minor: Optional[int] = None patch: Optional[int] = None itre = re.finditer(Versioning.CMAKE_VERSION_R, cmakefile, flags=re.MULTILINE) for cmake_version in itre: typ, value = cmake_version.groups() typ = typ.lower() if typ == "major": major = int(value) elif typ == "minor": minor = int(value) elif typ == "patch": patch = int(value) if any(e is None for e in (major, minor, patch)): return None return f"{major}.{minor}.{patch}" def get_version(self) -> str: if version := os.getenv("LIEF_VERSION_ENV"): return version if version := self.version_from_git(): return version if version := self.version_from_cmake(): return version raise RuntimeError("Can't determine version")