270 lines
10 KiB
Python
Executable File
270 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import argparse
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from functools import wraps
|
|
|
|
# This script is used to determine the current version of Memgraph. The script
|
|
# determines the current version using `git` automatically. The user can also
|
|
# manually specify a version override and then the supplied version will be
|
|
# used instead of the version determined by `git`. All versions (automatically
|
|
# detected and manually specified) can have a custom version suffix added to
|
|
# them.
|
|
#
|
|
# The current version can be one of either:
|
|
# - release version
|
|
# - development version
|
|
#
|
|
# The release version is either associated with a `release/X.Y` branch
|
|
# (automatic detection) or is manually specified. When the version is
|
|
# automatically detected from the branch a `.0` is appended to the version.
|
|
# Example 1:
|
|
# - branch: release/0.50
|
|
# - version: 0.50.0
|
|
# Example 2:
|
|
# - manually supplied version: 0.50.1
|
|
# - version: 0.50.1
|
|
#
|
|
# The development version is always determined using `git` in the following
|
|
# way:
|
|
# - release version - nearest (older) `release/X.Y` branch version
|
|
# - distance from the release branch - Z commits
|
|
# - the current commit short hash
|
|
# Example:
|
|
# - release version: 0.50.0 (nearest older branch `release/0.50`)
|
|
# - distance from the release branch: 42 (commits)
|
|
# - current commit short hash: 7e1eef94
|
|
#
|
|
# The script then uses the collected information to generate the versions that
|
|
# will be used in the binary, DEB package and RPM package. All of the versions
|
|
# have different naming conventions that have to be met and they differ among
|
|
# each other.
|
|
#
|
|
# The binary version is determined using the following two templates:
|
|
# Release version:
|
|
# <VERSION>-<OFFERING>[-<SUFFIX>]
|
|
# Development version:
|
|
# <VERSION>+<DISTANCE>~<SHORTHASH>-<OFFERING>[-<SUFFIX>]
|
|
# Examples:
|
|
# Release version:
|
|
# 0.50.1-open-source
|
|
# 0.50.1
|
|
# 0.50.1-veryimportantcustomer
|
|
# Development version (master, 12 commits after release/0.50):
|
|
# 0.50.0+12~7e1eef94-open-source
|
|
# 0.50.0+12~7e1eef94
|
|
# 0.50.0+12~7e1eef94-veryimportantcustomer
|
|
#
|
|
# The DEB package version is determined using the following two templates:
|
|
# Release version:
|
|
# <VERSION>-<OFFERING>[-<SUFFIX>]-1
|
|
# Development version (master, 12 commits after release/0.50):
|
|
# <VERSION>+<DISTANCE>~<SHORTHASH>-<OFFERING>[-<SUFFIX>]-1
|
|
# Examples:
|
|
# Release version:
|
|
# 0.50.1-open-source-1
|
|
# 0.50.1-1
|
|
# 0.50.1-veryimportantcustomer-1
|
|
# Development version (master, 12 commits after release/0.50):
|
|
# 0.50.0+12~7e1eef94-open-source-1
|
|
# 0.50.0+12~7e1eef94-1
|
|
# 0.50.0+12~7e1eef94-veryimportantcustomer-1
|
|
# For more documentation about the DEB package naming conventions see:
|
|
# https://www.debian.org/doc/debian-policy/ch-controlfields.html#version
|
|
#
|
|
# The RPM package version is determined using the following two templates:
|
|
# Release version:
|
|
# <VERSION>_1.<OFFERING>[.<SUFFIX>]
|
|
# Development version:
|
|
# <VERSION>_0.<DISTANCE>.<SHORTHASH>.<OFFERING>[.<SUFFIX>]
|
|
# Examples:
|
|
# Release version:
|
|
# 0.50.1_1.open-source
|
|
# 0.50.1_1
|
|
# 0.50.1_1.veryimportantcustomer
|
|
# Development version:
|
|
# 0.50.0_0.12.7e1eef94.open-source
|
|
# 0.50.0_0.12.7e1eef94
|
|
# 0.50.0_0.12.7e1eef94.veryimportantcustomer
|
|
# For more documentation about the RPM package naming conventions see:
|
|
# https://docs.fedoraproject.org/en-US/packaging-guidelines/Versioning/
|
|
# https://fedoraproject.org/wiki/Package_Versioning_Examples
|
|
|
|
|
|
def retry(retry_limit, timeout=100):
|
|
def inner_func(func):
|
|
@wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
for _ in range(retry_limit):
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except Exception:
|
|
time.sleep(timeout)
|
|
return func(*args, **kwargs)
|
|
return wrapper
|
|
return inner_func
|
|
|
|
|
|
@retry(3)
|
|
def get_output(*cmd, multiple=False):
|
|
ret = subprocess.run(cmd, stdout=subprocess.PIPE, check=True)
|
|
if multiple:
|
|
return list(map(lambda x: x.strip(), ret.stdout.decode("utf-8").strip().split("\n")))
|
|
return ret.stdout.decode("utf-8").strip()
|
|
|
|
|
|
def format_version(variant, version, offering, distance=None, shorthash=None, suffix=None):
|
|
if not distance:
|
|
# This is a release version.
|
|
if variant == "deb":
|
|
# <VERSION>-<OFFERING>[-<SUFFIX>]-1
|
|
ret = "{}{}".format(version, "-" + offering if offering else "")
|
|
if suffix:
|
|
ret += "-" + suffix
|
|
ret += "-1"
|
|
return ret
|
|
elif variant == "rpm":
|
|
# <VERSION>_1.<OFFERING>[.<SUFFIX>]
|
|
ret = "{}_1{}".format(version, "." + offering if offering else "")
|
|
if suffix:
|
|
ret += "." + suffix
|
|
return ret
|
|
else:
|
|
# <VERSION>-<OFFERING>[-<SUFFIX>]
|
|
ret = "{}{}".format(version, "-" + offering if offering else "")
|
|
if suffix:
|
|
ret += "-" + suffix
|
|
return ret
|
|
else:
|
|
# This is a development version.
|
|
if variant == "deb":
|
|
# <VERSION>+<DISTANCE>~<SHORTHASH>-<OFFERING>[-<SUFFIX>]-1
|
|
ret = "{}+{}~{}{}".format(version, distance, shorthash, "-" + offering if offering else "")
|
|
if suffix:
|
|
ret += "-" + suffix
|
|
ret += "-1"
|
|
return ret
|
|
elif variant == "rpm":
|
|
# <VERSION>_0.<DISTANCE>.<SHORTHASH>.<OFFERING>[.<SUFFIX>]
|
|
ret = "{}_0.{}.{}{}".format(version, distance, shorthash, "." + offering if offering else "")
|
|
if suffix:
|
|
ret += "." + suffix
|
|
return ret
|
|
else:
|
|
# <VERSION>+<DISTANCE>~<SHORTHASH>-<OFFERING>[-<SUFFIX>]
|
|
ret = "{}+{}~{}{}".format(version, distance, shorthash, "-" + offering if offering else "")
|
|
if suffix:
|
|
ret += "-" + suffix
|
|
return ret
|
|
|
|
|
|
# Parse arguments.
|
|
parser = argparse.ArgumentParser(description="Get the current version of Memgraph.")
|
|
parser.add_argument("--open-source", action="store_true", help="set the current offering to 'open-source'")
|
|
parser.add_argument("version", help="manual version override, if supplied the version isn't " "determined using git")
|
|
parser.add_argument("suffix", help="custom suffix for the current version being built")
|
|
parser.add_argument(
|
|
"--variant",
|
|
choices=("binary", "deb", "rpm"),
|
|
default="binary",
|
|
help="which variant of the version string should be generated",
|
|
)
|
|
parser.add_argument(
|
|
"--memgraph-root-dir", help="The root directory of the checked out " "Memgraph repository.", default="."
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
if not os.path.isdir(args.memgraph_root_dir):
|
|
raise Exception("The root directory ({}) is not a valid directory".format(args.memgraph_root_dir))
|
|
|
|
os.chdir(args.memgraph_root_dir)
|
|
|
|
offering = "open-source" if args.open_source else None
|
|
|
|
# Check whether the version was manually supplied.
|
|
if args.version:
|
|
if not re.match(r"^[0-9]+\.[0-9]+\.[0-9]+$", args.version):
|
|
raise Exception("Invalid version supplied '{}'!".format(args.version))
|
|
print(format_version(args.variant, args.version, offering, suffix=args.suffix), end="")
|
|
sys.exit(0)
|
|
|
|
# Within CI, after the regular checkout, master is sometimes (e.g. in the case
|
|
# of an epic or task branch) NOT created as a local branch. cpack depends on
|
|
# variables generated by calling this script during the cmake phase. This
|
|
# script needs master to be the local branch. `git fetch origin master:master`
|
|
# is creating the local master branch without checking it out. Does nothing if
|
|
# master is already there.
|
|
try:
|
|
current_branch = get_output("git", "rev-parse", "--abbrev-ref", "HEAD")
|
|
if current_branch != "master":
|
|
branches = get_output("git", "branch")
|
|
if "master" in branches:
|
|
# If master is present locally, the fetch is allowed to fail
|
|
# because this script will still be able to compare against the
|
|
# master branch.
|
|
try:
|
|
get_output("git", "fetch", "origin", "master:master")
|
|
except Exception:
|
|
pass
|
|
else:
|
|
# If master is not present locally, the fetch command has to
|
|
# succeed because something else will fail otherwise.
|
|
get_output("git", "fetch", "origin", "master:master")
|
|
except Exception:
|
|
print("Fatal error while ensuring local master branch.")
|
|
sys.exit(1)
|
|
|
|
# Get current commit hashes.
|
|
current_hash = get_output("git", "rev-parse", "HEAD")
|
|
current_hash_short = get_output("git", "rev-parse", "--short", "HEAD")
|
|
|
|
# We want to find branches that exist on some remote and that are named
|
|
# `release/[0-9]+\.[0-9]+`.
|
|
branch_regex = re.compile(r"^remotes/[a-zA-Z0-9]+/release/([0-9]+\.[0-9]+)$")
|
|
|
|
# Find all existing versions.
|
|
versions = []
|
|
branches = get_output("git", "branch", "--all", multiple=True)
|
|
for branch in branches:
|
|
match = branch_regex.match(branch)
|
|
if match is not None:
|
|
version = tuple(map(int, match.group(1).split(".")))
|
|
master_branch_merge = get_output("git", "merge-base", "master", branch)
|
|
versions.append((version, branch, master_branch_merge))
|
|
versions.sort(reverse=True)
|
|
|
|
# Check which existing version branch is closest to the current commit. We are
|
|
# only interested in branches that were branched out before this commit was
|
|
# created.
|
|
current_version = None
|
|
for version in versions:
|
|
version_tuple, branch, master_branch_merge = version
|
|
current_branch_merge = get_output("git", "merge-base", current_hash, branch)
|
|
master_current_merge = get_output("git", "merge-base", current_hash, "master")
|
|
# The first check checks whether this commit is a child of `master` and
|
|
# the version branch was created before us.
|
|
# The second check checks whether this commit is a child of the version
|
|
# branch.
|
|
if master_branch_merge == current_branch_merge or master_branch_merge == master_current_merge:
|
|
current_version = version
|
|
break
|
|
|
|
# Determine current version.
|
|
if current_version is None:
|
|
raise Exception("You are attempting to determine the version for a very " "old version of Memgraph!")
|
|
version, branch, master_branch_merge = current_version
|
|
distance = int(get_output("git", "rev-list", "--count", "--first-parent", master_branch_merge + ".." + current_hash))
|
|
version_str = ".".join(map(str, version)) + ".0"
|
|
if distance == 0:
|
|
print(format_version(args.variant, version_str, offering, suffix=args.suffix), end="")
|
|
else:
|
|
print(
|
|
format_version(
|
|
args.variant, version_str, offering, distance=distance, shorthash=current_hash_short, suffix=args.suffix
|
|
),
|
|
end="",
|
|
)
|