"""Version information.""" import errno import os import subprocess from importlib import import_module _ver = None def get_version(project, repo_file, api_version=None): """Determine a project's version information. Standardized version retrieval for git-based projects. In summary, if the api_version isn't specified it imports __version__ from the main module for the project. Next it tries to import extended information from a generated file (for packages using snakeoil's custom sdist phase) and if that fails assumes it's in a git repo and grabs the git info instead. :param project: module name :param repo_file: file belonging to module :param api_version: version for the project, if not specified __version__ is imported from the main project module :return: a string describing the project version """ global _ver # pylint: disable=global-statement if _ver is None: version_info = None if api_version is None: try: api_version = getattr(import_module(project), "__version__") except ImportError: raise ValueError(f"no {project} module in the syspath") try: version_info = getattr(import_module(f"{project}._verinfo"), "version_info") except ImportError: # we're probably in a git repo path = os.path.dirname(os.path.abspath(repo_file)) version_info = get_git_version(path) if version_info is None: s = "" elif version_info["tag"] == api_version: s = f" -- released {version_info['date']}" else: rev = version_info["rev"][:7] date = version_info["date"] commits = version_info.get("commits", None) commits = f"-{commits}" if commits is not None else "" s = f"{commits}-g{rev} -- {date}" _ver = f"{project} {api_version}{s}" return _ver def _run_git(path, cmd): env = dict(os.environ) for key in env.copy(): # pragma: no cover if key.startswith("LC_"): del env[key] env["LC_CTYPE"] = "C" env["LC_ALL"] = "C" r = subprocess.Popen( ["git"] + list(cmd), stdout=subprocess.PIPE, env=env, stderr=subprocess.DEVNULL, cwd=path, ) stdout = r.communicate()[0] return stdout, r.returncode def get_git_version(path): """Return git related revision data.""" path = os.path.abspath(path) try: stdout, ret = _run_git(path, ["log", "--format=%H\n%aD", "HEAD^..HEAD"]) if ret != 0: return None data = stdout.decode().splitlines() tag = _get_git_tag(path, data[0]) # get number of commits since most recent tag stdout, ret = _run_git(path, ["describe", "--tags", "--abbrev=0"]) prev_tag = None commits = None if ret == 0: prev_tag = stdout.decode().strip() stdout, ret = _run_git(path, ["log", "--oneline", f"{prev_tag}..HEAD"]) if ret == 0: commits = len(stdout.decode().splitlines()) return { "rev": data[0], "date": data[1], "tag": tag, "commits": commits, } except EnvironmentError as exc: # ENOENT is thrown when the git binary can't be found. if exc.errno != errno.ENOENT: raise return None def _get_git_tag(path, rev): stdout, _ = _run_git(path, ["name-rev", "--tag", rev]) tag = stdout.decode().split() if len(tag) != 2: return None tag = tag[1] if not tag.startswith("tags/"): return None tag = tag[len("tags/") :] if tag.endswith("^0"): tag = tag[:-2] if tag.startswith("v"): tag = tag[1:] return tag