Skip to content

Commit

Permalink
version.py: generate better version numbers (based on most recent r…
Browse files Browse the repository at this point in the history
…elease tag)
  • Loading branch information
ryan-williams committed May 14, 2024
1 parent 0c7ce82 commit fb55430
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 42 deletions.
2 changes: 1 addition & 1 deletion apis/python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,5 +339,5 @@ def run(self):
},
python_requires=">=3.8",
cmdclass={"build_ext": build_ext, "bdist_wheel": bdist_wheel},
version=version.getVersion(),
version=version.get_version(),
)
131 changes: 90 additions & 41 deletions apis/python/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,60 +71,105 @@

import os
import re
import subprocess
import shlex
from typing import Optional

import sys
from datetime import date
from os.path import basename
from subprocess import CalledProcessError, check_output
from subprocess import CalledProcessError, check_output, DEVNULL

RELEASE_VERSION_FILE = os.path.join(os.path.dirname(__file__), "RELEASE-VERSION")

# http://www.python.org/dev/peps/pep-0386/
_PEP386_SHORT_VERSION_RE = r"\d+(?:\.\d+)+(?:(?:[abc]|rc)\d+(?:\.\d+)*)?"
_PEP386_VERSION_RE = r"^%s(?:\.post\d+)?(?:\.dev\d+)?$" % _PEP386_SHORT_VERSION_RE
_GIT_DESCRIPTION_RE = r"^(?P<ver>%s)-(?P<commits>\d+)-g(?P<sha>[\da-f]+)$" % (
_PEP386_SHORT_VERSION_RE
)
_GIT_DESCRIPTION_RE = r"^(?P<ver>%s)-(?P<commits>\d+)-g(?P<sha>[\da-f]+)$" % _PEP386_SHORT_VERSION_RE


def err(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)


def readGitVersion():
def lines(*cmd, drop_trailing_newline: bool = True, **kwargs) -> list[str]:
lns = [ l.rstrip('\n') for l in check_output(cmd, **kwargs).decode().splitlines() ]
if lns and drop_trailing_newline and not lns[-1]:
lns.pop()
return lns


def line(*cmd, **kwargs) -> Optional[str]:
lns = lines(*cmd, **kwargs)
if len(lns) != 1:
raise RuntimeError(f"Expected 1 line, found {len(lns)}: {shlex.join(cmd)}")
return lns[0]


def get_latest_tag() -> Optional[str]:
tags = lines("git", "tag", "--list", "--sort=v:refname", "[0-9].*.*")
return tags[-1] if tags else None


def get_latest_remote_tag(remote: str) -> str:
tags = lines("git", "ls-remote", "--tags", "--sort=v:refname", remote, "[0-9].*.*")
if not tags:
raise RuntimeError(f"No tags found in remote {remote}")
return tags[-1].split(' ')[-1].split('/')[-1]


def get_sha_base10() -> int:
sha = line("git", "log", "-1", "--format=%h")
return int(sha, 16)


def get_git_version() -> Optional[str]:
# NOTE: this will fail if on a fork with unsynchronized tags.
# use `git fetch --tags upstream`
# and `git push --tags <your fork>`
try:
proc = subprocess.Popen(
("git", "describe", "--long", "--tags", "--match", "[0-9]*.*"),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
data, stderr = proc.communicate()
if proc.returncode:
return None
ver = data.decode().splitlines()[0].strip()
git_version = line("git", "describe", "--long", "--tags", "--match", "[0-9]*.*", stderr=DEVNULL)
except CalledProcessError:
return None

if not ver:
return None
m = re.search(_GIT_DESCRIPTION_RE, ver)
if not m:
err(
"version: git description (%s) is invalid, " "ignoring\n" % ver,
)
return None

commits = int(m.group("commits"))
if not commits:
return m.group("ver")
git_version = None

ver = None
commits = None
if git_version:
m = re.search(_GIT_DESCRIPTION_RE, git_version)
if m:
commits = int(m.group("commits"))
ver = m.group("ver")

# `1.5.0` (Nov '23) is typically the most recent tag that's an ancestor of `main`; subsequent
# release tags all exist on release branches (by design).
#
# If `git describe` above returned `1.5.0` as the nearest tagged ancestor, synthesize a
# more meaningful version number below:
#
# 1. Find the latest release tag in the local repo (or tracked remote, if there are no local
# tags, e.g. in case of a shallow clone).
# 2. Return a PEP440-compatible version of the form `A.B.C.post0.devN`, where:
# - `A.B.C` is the most recent release tag in the repo, and
# - `N` is the current short Git SHA, converted to base 10.
if not ver or ver.startswith("1.5.0"):
latest_tag = get_latest_tag()
if latest_tag:
err(f"Git traversal returned {ver}, using latest local tag {latest_tag}")
else:
tracked_branch = line("git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}")
tracked_remote = tracked_branch.split("/")[0]
latest_tag = get_latest_remote_tag(tracked_remote)
err(f"Git traversal returned {ver}, using latest tag {latest_tag} from tracked remote {tracked_remote}")

ver = f"{latest_tag}.post0.dev{get_sha_base10()}"
return ver
else:
return "%s.post%d.dev%d" % (m.group("ver"), commits, int(m.group("sha"), 16))
if not commits:
return ver
else:
return "%s.post%d.dev%d" % (m.group("ver"), commits, int(m.group("sha"), 16))


def readReleaseVersion():
def read_release_version():
try:
with open(RELEASE_VERSION_FILE) as fd:
ver = fd.readline().strip()
Expand All @@ -138,26 +183,30 @@ def readReleaseVersion():
return None


def generateCalVersion():
def generate_cal_version():
today = date.today().strftime("%Y.%m.%d")
sha = check_output(["git", "log", "-1", "--format=%h"]).decode().rstrip("\n")
sha_dec = int(sha, 16)
return f"{today}.dev{sha_dec}"
return f"{today}.dev{get_sha_base10()}"


def writeReleaseVersion(version):
def write_release_version(version):
with open(RELEASE_VERSION_FILE, "w") as fd:
print(version, file=fd)


def getVersion():
release_version = readReleaseVersion()
version = readGitVersion() or release_version
def get_version():
release_version = read_release_version()
version = get_git_version()
if not version:
version = generateCalVersion()
version = release_version
if not version:
version = generate_cal_version()
err(
f"No {basename(RELEASE_VERSION_FILE)} or Git version found, using calver {version}"
)
if version != release_version:
writeReleaseVersion(version)
write_release_version(version)
return version


if __name__ == '__main__':
print(get_version())

0 comments on commit fb55430

Please sign in to comment.