memgraph/release/get_version.py
andrejtonev 8b9e1fa08b
Replication refactor part 6 (#1484)
Single (instance level) connection to a replica (messages from all databases get multiplexed through it)
ReplicationClient split in two: ReplicationClient and ReplicationStorageClient
New ReplicationClient, moved under replication, handles the raw connection, owned by MainRoleData
ReplicationStorageClient handles the storage <-> replica state machine and holds to a stream
Removed epoch and storage from *Clients
rpc::Stream proactively aborts on error and sets itself to a defunct state
Removed HandleRpcFailure, instead we simply log the error and let the FrequentCheck handle re-connection
replica_state is now a synced variable
ReplicaStorageClient state machine bugfixes
Single FrequentCheck that goes through DBMS
Moved ReplicationState under DbmsHandler
Moved some replication startup logic under the DbmsHandler's constructor
Removed InMemoryReplicationClient
CreateReplicationClient has been removed from Storage
Simplified GetRecoverySteps and made safer

---------

Co-authored-by: Gareth Lloyd <gareth.lloyd@memgraph.io>
2023-11-23 11:02:35 +01:00

272 lines
11 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", "-r", "--list", "origin/master")
if "origin/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")
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")
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", "origin/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, "origin/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="",
)