LUMIERA.clone/admin/buildVersion.py
Ichthyostega a34b1f6ddd Bugfix: setVersion.py -- ability to handle suffixes
Turns out that in practice there will be (at least temporarily)
some version-tags including a suffix: the RC-versions!

Now there is the special twist, that Git does not allow '~' in Tag names,
and thus `git-buildpackage` introduced an additional layer of translation.
So we are forced to revert that translation, which is possible,
since the basic Debian version syntax disallows '_' in version numbers
(because '_' is used to separate the package name from the version number).

It seems prudent to implement that as an preprocessing step
and thus keep it out of the regular version number syntax.

Furthermore we need the ability to handle existing suffixes,
which (as we know now) can be picked up from the Git history.
 * my decision is to allow both pass-through and suppressing them
 * use `--suffix=False` to suppress / remove any existing suffix
 * the latter now allows us also to automate the setting of
   the final Release version
2025-11-13 22:06:06 +01:00

183 lines
5.3 KiB
Python
Executable file
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/python3
# coding: utf-8
##
## buildVersion.py - extract and possibly bump current version from Git
##
# Copyright (C)
# 2025, Hermann Vosseler <Ichthyostega@web.de>
#
# **Lumiera** is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version. See the file COPYING for further details.
#####################################################################
'''
Build and possibly bump a current project version spec,
based on the nearest Git tag.
'''
import re
import os
import sys
import datetime
import argparse
import subprocess
#------------CONFIGURATION----------------------------
CMDNAME = os.path.basename(__file__)
TAG_PAT = 'v*.*'
VER_SEP = r'(?:^v?|\.)'
VER_NUM = r'(\w[\w\+]*)'
VER_SUB = r'(?:'+VER_SEP+VER_NUM+')'
VER_SUF = r'(?:~('+VER_NUM+VER_SUB+'?'+'))'
VER_SYNTAX = VER_SUB +VER_SUB+'?' +VER_SUB+'?' +VER_SUF+'?'
GIT = 'git'
#------------CONFIGURATION----------------------------
def parseAndBuild():
''' main: parse cmdline and generate version string '''
parser = argparse.ArgumentParser (prog=CMDNAME, description='%s: %s' % (CMDNAME, __doc__)
,formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument ('--bump','-b'
,nargs='?'
,choices=['maj','min','rev'], const='rev'
,help='bump the version detected from Git (optionally bump a specific component)')
parser.add_argument ('--suffix','-s'
,help='append (or replace) a suffix (attached with ~); False -> remove suffix')
parser.add_argument ('--snapshot'
,action='store_true'
,help='mark as development snapshot by appending ~dev.YYYYMMDDhhmm, using UTC date from HEAD commit')
opts = parser.parse_args()
version = getVersionFromGit()
version = rebuild (version, **vars(opts))
print (version)
def getVersionFromGit():
get_nearest_matching_tag = 'describe --tags --abbrev=0 --match=' + TAG_PAT
return runGit (get_nearest_matching_tag)
def getTimestampFromGit():
get_head_author_date = 'show -s --format=%ai'
timespec = runGit (get_head_author_date)
timespec = datetime.datetime.fromisoformat (timespec)
timespec = timespec.astimezone (datetime.timezone.utc) # note: convert into UTC
return timespec.strftime ('%Y%m%d%H%M')
def parseVerNr (verStr):
""" parse a version spec from a git tag,
possibly preprocess to translate _ -> ~
"""
NOT_SFX = r'(?:[^_\W]|[\.\+])+'
DECODE = r'('+NOT_SFX+')(?:_('+NOT_SFX+'))?'
#
mat = re.fullmatch (DECODE, verStr)
if not mat:
__FAIL ('version string contains invalid characters: "'+verStr+'"')
verStr = mat.group(1)
if mat.group(2):
verStr += '~'+mat.group(2)
#
# check syntax of translated version spec
mat = re.fullmatch (VER_SYNTAX, verStr)
if not mat:
__FAIL ('invalid version syntax in "'+verStr+'"')
else:
return mat
def rebuild (version, bump=None, suffix=None, snapshot=False):
mat = parseVerNr (version)
maj = mat.group(1)
min = mat.group(2)
rev = mat.group(3)
suf = mat.group(4)
suf_idi = mat.group(5) # detail structure not used (as of 2025)
suf_num = mat.group(6)
if bump=='maj':
maj = bumpedNum(maj)
min = None
rev = None
elif bump=='min':
min = bumpedNum(min)
rev = None
elif bump=='rev':
rev = bumpedNum(rev)
if snapshot:
suf = 'dev.'+getTimestampFromGit()
elif suffix:
if not evalBool(suffix):
suf = None
else:
suf = suffix
version = maj
if min:
version += '.'+min
elif not min and rev:
version += '.0'
if rev:
version += '.'+rev
if suf:
version += '~'+suf
return version
def bumpedNum (verStr):
mat = re.match (r'\d+', str(verStr))
if not mat:
return '1'
numStr = mat.group(0)
num = int(numStr) + 1
return str(num).zfill(len(numStr))
def runGit (argStr):
''' run Git as system command without shell and retrieve the output '''
argList = [GIT] + argStr.split()
try:
proc = subprocess.run (argList, check=True, capture_output=True, encoding='utf-8', env={'LC_ALL':'C'})
return proc.stdout.rstrip() # Note: sanitised env
except:
__FAIL ('invoking git '+argStr)
def evalBool (val) ->bool:
""" evaluate as bool value
@author: Tim Poulsen
@note: Adapted from the original, published 2023, CC-By-SA-4
https://www.timpoulsen.com/2023/python-bool-from-any.html
"""
try:
return float(val) > 0
except:
if type(val) is str:
return val.lower() not in ['false', 'no', 'n', 'none', 'null']
else:
# rely on Python's type coercion rules
return bool(val)
def __ERR (*args, **kwargs):
print (*args, file=sys.stderr, **kwargs)
def __FAIL (msg):
__ERR ("FAILURE: "+msg)
exit (-1)
if __name__=='__main__':
parseAndBuild()