lvgl/scripts/update_version.py
2024-09-20 10:42:16 +02:00

178 lines
5.7 KiB
Python
Executable File

#!/usr/bin/env python3
import os
import re
import argparse
from typing import List, Union
def get_arg():
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description=""
"Apply the specified version to affected source files. Eg.:\n"
" python3 update_version.py 9.1.2-dev\n"
" python3 update_version.py 9.2.0"
)
parser.add_argument('version', metavar='version', type=str,
help='The version to apply')
return parser.parse_args()
class Version:
RE_PATTERN = r"(\d+)\.(\d+)\.(\d+)(-[\w\d]+)?"
def __init__(self, user_input: str):
if not re.match(r'^' + self.RE_PATTERN + r'$', user_input):
raise Exception(f"Invalid version format: {user_input}")
groups = re.search(self.RE_PATTERN, user_input).groups()
self.major = groups[0]
self.minor = groups[1]
self.patch = groups[2]
self.info = groups[3].lstrip('-') if groups[3] else ""
self.is_release = len(self.info) == 0
self.as_string = user_input
def __str__(self):
return self.as_string
class RepoFileVersionReplacer:
DIR_SCRIPTS = os.path.dirname(__file__)
DIR_REPO_ROOT = os.path.join(DIR_SCRIPTS, "..")
def __init__(self, relative_path_segments: List[str], expected_occurrences: int):
self.path_relative = os.path.join(*relative_path_segments)
self.path = os.path.join(self.DIR_REPO_ROOT, self.path_relative)
self.expected_occurrences = expected_occurrences
def applyVersionToLine(self, line: str, version: Version) -> Union[str, None]:
return None
def applyVersion(self, version: Version):
with open(self.path, 'r', encoding='utf-8') as file:
lines = file.readlines()
occurrences = 0
for i, line in enumerate(lines):
line_with_version = self.applyVersionToLine(line, version)
if line_with_version:
lines[i] = line_with_version
occurrences += 1
# not perfect, but will catch obvious pitfalls
if occurrences != self.expected_occurrences:
raise Exception(f"Bad lines in {self.path_relative}")
with open(self.path, 'w', encoding='utf-8') as file:
file.writelines(lines)
class PrefixReplacer(RepoFileVersionReplacer):
def __init__(self, relative_path_segments: List[str], prefix: str, expected_occurrences=1):
super().__init__(relative_path_segments, expected_occurrences)
self.prefix = prefix
def applyVersionToLine(self, line: str, version: Version):
pattern = r'(' + re.escape(self.prefix) + ')' + Version.RE_PATTERN
repl = r'\g<1>' + str(version)
replaced, n = re.subn(pattern, repl, line)
return replaced if n > 0 else None
class MacroReplacer(RepoFileVersionReplacer):
def __init__(self, relative_path_segments: List[str]):
super().__init__(relative_path_segments, 4)
def applyVersionToLine(self, line: str, version: Version):
targets = {
'LVGL_VERSION_MAJOR': version.major,
'LVGL_VERSION_MINOR': version.minor,
'LVGL_VERSION_PATCH': version.patch,
'LVGL_VERSION_INFO': version.info,
}
for key, val in targets.items():
pattern = self.getPattern(key)
repl = self.getReplacement(val)
replaced, n = re.subn(pattern, repl, line)
if n > 0:
return replaced
return None
def getPattern(self, key: str):
return r'(^#define ' + key + r' +).+'
def getReplacement(self, val: str):
if not val.isnumeric():
val = f'"{val}"'
return r'\g<1>' + val
class CmakeReplacer(MacroReplacer):
def getPattern(self, key: str):
return r'(^set\(' + key + r' +")([^"]*)(.+)'
def getReplacement(self, val: str):
return r'\g<1>' + val + r'\g<3>'
class KconfigReplacer(RepoFileVersionReplacer):
"""Replace version info in Kconfig file"""
def __init__(self, relative_path_segments: List[str]):
super().__init__(relative_path_segments, 3)
def applyVersionToLine(self, line: str, version: Version):
targets = {
'LVGL_VERSION_MAJOR': version.major,
'LVGL_VERSION_MINOR': version.minor,
'LVGL_VERSION_PATCH': version.patch,
}
for key, val in targets.items():
pattern = self.getPattern(key)
repl = self.getReplacement(val)
replaced, n = re.subn(pattern, repl, line)
if n > 0:
return replaced
return None
def getPattern(self, key: str):
# Match the version fields in Kconfig file
return rf'(^\s+default\s+)(\d+) # ({key})'
def getReplacement(self, val: str):
# Replace the version value
return r'\g<1>' + val + r' # \g<3>'
if __name__ == '__main__':
args = get_arg()
version = Version(args.version)
print(f"Applying version {version} to:")
targets = [
MacroReplacer(['lv_version.h']),
CmakeReplacer(['env_support', 'cmake', 'version.cmake']),
PrefixReplacer(['lv_conf_template.h'], 'Configuration file for v'),
KconfigReplacer(['Kconfig']),
]
if version.is_release:
targets.extend([
PrefixReplacer(['library.json'], '"version": "'),
PrefixReplacer(['library.properties'], 'version='),
PrefixReplacer(['Kconfig'], 'Kconfig file for LVGL v'),
])
for target in targets:
print(f" - {target.path_relative}")
target.applyVersion(version)