mirror of
https://github.com/Fishwaldo/obs-service-set_version.git
synced 2025-03-15 19:31:34 +00:00
378 lines
13 KiB
Python
Executable file
378 lines
13 KiB
Python
Executable file
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# A simple script to update version number in spec, dsc or arch linux files
|
|
#
|
|
# (C) 2010 by Adrian Schröter <adrian@suse.de>
|
|
# (C) 2015 by Thomas Bechtold <tbechtold@suse.com>
|
|
#
|
|
# This program 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 http://www.gnu.org/licenses/gpl-2.0.html for full license text.
|
|
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import glob
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
import tarfile
|
|
import zipfile
|
|
|
|
try:
|
|
from packaging.version import LegacyVersion, Version, parse
|
|
except ImportError as exc:
|
|
HAS_PACKAGING = False
|
|
import warnings
|
|
warnings.warn("install 'packaging' to improve python package versions",
|
|
RuntimeWarning)
|
|
else:
|
|
HAS_PACKAGING = True
|
|
|
|
|
|
outdir = None
|
|
suffixes = ('obscpio', 'tar', 'tar.gz', 'tgz', 'tar.bz2', 'tbz2', 'tar.xz',
|
|
'zip')
|
|
suffixes_re = "|".join(map(lambda x: re.escape(x), suffixes))
|
|
|
|
|
|
def _get_local_files():
|
|
""" sorted local file list by modification time (newest first)"""
|
|
files = glob.glob('*')
|
|
files.sort(key=lambda x: os.stat(os.path.join(os.getcwd(), x)).st_mtime,
|
|
reverse=True)
|
|
return files
|
|
|
|
|
|
class VersionDetector(object):
|
|
@staticmethod
|
|
def _autodetect(files, basename):
|
|
version = VersionDetector._get_version_via_obsinfo(
|
|
files, basename)
|
|
if not version:
|
|
version = VersionDetector._get_version_via_archive_dirname(
|
|
files, basename)
|
|
if not version:
|
|
version = VersionDetector._get_version_via_filename(
|
|
files, basename)
|
|
if not version:
|
|
version = VersionDetector._get_version_via_debian_changelog(
|
|
"debian.changelog")
|
|
return version
|
|
|
|
@staticmethod
|
|
def _get_version_via_filename(files, basename):
|
|
""" detect version based on file names"""
|
|
for f in files:
|
|
regex = r"^%s.*[-_]([\d].*)\.(?:%s)$" % (re.escape(basename),
|
|
suffixes_re)
|
|
m = re.match(regex, f)
|
|
if m:
|
|
return m.group(1)
|
|
# Nothing found
|
|
return None
|
|
|
|
@staticmethod
|
|
def __get_version(str_list, basename):
|
|
regex = "%s.*[-_]([\d][^\/]*).*" % basename
|
|
for s in str_list:
|
|
m = re.match(regex, s)
|
|
if m:
|
|
return m.group(1)
|
|
# Nothing found
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_version_via_archive_dirname(files, basename):
|
|
""" detect version based tar'd directory name"""
|
|
for f in filter(lambda x: x.endswith(suffixes), files):
|
|
# handle tarfiles
|
|
if tarfile.is_tarfile(f):
|
|
with tarfile.open(f) as tf:
|
|
v = VersionDetector.__get_version(tf.getnames(), basename)
|
|
if v:
|
|
return v
|
|
# handle zipfiles
|
|
if zipfile.is_zipfile(f):
|
|
with zipfile.ZipFile(f, 'r') as zf:
|
|
v = VersionDetector.__get_version(zf.namelist(), basename)
|
|
if v:
|
|
return v
|
|
# Nothing found
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_version_via_obsinfo(files, basename):
|
|
join_suffix = basename + ".obsinfo"
|
|
for filename in filter(lambda x: x.endswith(join_suffix), files):
|
|
if os.path.exists(filename):
|
|
with open(filename, "r") as fp:
|
|
for line in fp:
|
|
if line.startswith("version: "):
|
|
string = line[9:]
|
|
string = string.rstrip()
|
|
return string
|
|
# Nothing found
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_version_via_debian_changelog(filename):
|
|
# from http://anonscm.debian.org/cgit/pkg-python-debian/\
|
|
# python-debian.git/tree/lib/debian/changelog.py
|
|
topline = re.compile(r'^(\w%(name_chars)s*) \(([^\(\) \t]+)\)'
|
|
'((\s+%(name_chars)s+)+)\;'
|
|
% {'name_chars': '[-+0-9a-z.]'},
|
|
re.IGNORECASE)
|
|
if os.path.exists(filename):
|
|
with open(filename, "r") as f:
|
|
firstline = f.readline()
|
|
topmatch = topline.match(firstline)
|
|
if topmatch:
|
|
return topmatch.group(2)
|
|
# Nothing found
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_version_via_debian_dsc(filename):
|
|
version = re.compile(r'^Version:([ \t\f\v]*)[^%\n\r]*', re.IGNORECASE)
|
|
if os.path.exists(filename):
|
|
with open(filename, "r") as f:
|
|
for line in f:
|
|
versionmatch = version.match(line)
|
|
if versionmatch:
|
|
return versionmatch.group(0)
|
|
# Nothing found
|
|
return None
|
|
|
|
|
|
class PackageTypeDetector(object):
|
|
@staticmethod
|
|
def _get_package_type(files):
|
|
pt_found = False
|
|
for f in filter(lambda x: x.endswith(suffixes), files):
|
|
pt_found = PackageTypeDetector._is_python(f)
|
|
if pt_found:
|
|
return "python"
|
|
# no package type found
|
|
return None
|
|
|
|
@staticmethod
|
|
def _is_python(f):
|
|
names = []
|
|
if tarfile.is_tarfile(f):
|
|
with tarfile.open(f) as tf:
|
|
names = tf.getnames()
|
|
if zipfile.is_zipfile(f):
|
|
with zipfile.ZipFile(f, 'r') as zf:
|
|
names = zf.namelist()
|
|
for n in map(lambda x: os.path.normpath(x), names):
|
|
if n.endswith("egg-info/PKG-INFO"):
|
|
return True
|
|
return False
|
|
|
|
|
|
def _replace_define(filename, def_name, def_value, add_if_missing=True):
|
|
# first, modify a copy of filename and then move it
|
|
with open(filename, 'r+') as f:
|
|
contents = f.read()
|
|
f.seek(0)
|
|
contents_new, subs = re.subn(
|
|
r'^%define {def_name}(\s*)[^%].*'.format(
|
|
def_name=def_name),
|
|
r'%define {def_name}\g<1>{def_value}'.format(
|
|
def_name=def_name, def_value=def_value),
|
|
contents, flags=re.MULTILINE)
|
|
if subs == 0 and add_if_missing:
|
|
# seems there was no define. add new one before 'Name:'
|
|
contents_new, subs = re.subn(
|
|
r'^(Name:.*)$',
|
|
r'%define {def_name} {def_value}\n\n\g<1>'.format(
|
|
def_name=def_name, def_value=def_value),
|
|
contents, flags=re.MULTILINE)
|
|
|
|
f.truncate()
|
|
f.write(contents_new)
|
|
|
|
|
|
def _replace_spec_setup(filename, version_define):
|
|
# first, modify a copy of filename and then move it
|
|
with open(filename, 'r+') as f:
|
|
contents = f.read()
|
|
f.seek(0)
|
|
# %setup without "-n" uses implicit "-n" as "%{name}-%{version}"
|
|
contents_new, subs = re.subn(
|
|
r'^%setup\s*((?:-q)?)?\s*$',
|
|
r'%setup \1 -n %{{name}}-%{{{version_define}}}'.format(
|
|
version_define=version_define),
|
|
contents, flags=re.MULTILINE)
|
|
if subs == 0:
|
|
# keep inline macros for rpm
|
|
contents_new, subs = re.subn(
|
|
r'^%setup(.*)%{version}(.*)$',
|
|
r'%setup\g<1>%{{{version_define}}}\g<2>'.format(
|
|
version_define=version_define),
|
|
contents, flags=re.MULTILINE)
|
|
if subs > 0:
|
|
f.truncate()
|
|
f.write(contents_new)
|
|
|
|
|
|
def _replace_tag(filename, tag, string):
|
|
# first, modify a copy of filename and then move it
|
|
with open(filename, 'r+') as f:
|
|
contents = f.read()
|
|
f.seek(0)
|
|
if filename.endswith("PKGBUILD") or filename.endswith("build.collax"):
|
|
contents_new, subs = re.subn(
|
|
r"^{tag}=.*".format(tag=tag),
|
|
r"{tag}={string}".format(tag=tag, string=string), contents,
|
|
flags=re.MULTILINE)
|
|
else:
|
|
# keep inline macros for rpm
|
|
contents_new, subs = re.subn(
|
|
r'^{tag}:([ \t\f\v]*)[^%\n\r]*'.format(tag=tag),
|
|
r'{tag}:\g<1>{string}'.format(
|
|
tag=tag, string=string),
|
|
contents, flags=re.MULTILINE)
|
|
if subs > 0:
|
|
f.truncate()
|
|
f.write(contents_new)
|
|
|
|
|
|
def _replace_debian_changelog_version(filename, version_new):
|
|
# first, modify a copy of filename and then move it
|
|
# get current version
|
|
version_current = VersionDetector._get_version_via_debian_changelog(
|
|
filename)
|
|
with open(filename, 'r+') as f:
|
|
content_lines = f.readlines()
|
|
f.seek(0)
|
|
content_lines[0] = content_lines[0].replace(
|
|
version_current, version_new, 1)
|
|
f.truncate()
|
|
f.writelines(content_lines)
|
|
|
|
|
|
def _version_python_pip2rpm(version_pip):
|
|
"""generate a rpm compatible version from a python pip version"""
|
|
version_rpm = version_pip
|
|
if not HAS_PACKAGING:
|
|
return version_rpm
|
|
|
|
v = parse(version_pip)
|
|
if isinstance(v, Version):
|
|
if v.is_prerelease:
|
|
v_rpm = v.public
|
|
# we need to add the 'x' in front of alpha/beta release because
|
|
# in the python world, "1.1a10" > "1.1.dev10"
|
|
# but in the rpm world, "1.1~a10" < "1.1~dev10"
|
|
v_rpm = v_rpm.replace('a', '~xalpha')
|
|
v_rpm = v_rpm.replace('b', '~xbeta')
|
|
v_rpm = v_rpm.replace('rc', '~xrc')
|
|
v_rpm = v_rpm.replace('.dev', '~dev')
|
|
version_rpm = v_rpm
|
|
elif isinstance(v, LegacyVersion):
|
|
# TODO(toabctl): handle setuptools style legacy version
|
|
pass
|
|
|
|
return version_rpm
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description='Open Build Service source service "set_version".'
|
|
'Used to update build description files with a '
|
|
'detected or given version number.')
|
|
parser.add_argument('--outdir', required=True,
|
|
help='output directory for modified sources')
|
|
parser.add_argument('--version',
|
|
help='use given version string, do not detect it '
|
|
'from source files')
|
|
parser.add_argument('--basename', default="",
|
|
help='detect version based on the file name with '
|
|
'a given prefix')
|
|
parser.add_argument('--file', action='append',
|
|
help='modify only this build description. '
|
|
'maybe used multiple times.')
|
|
args = vars(parser.parse_args())
|
|
|
|
version = args['version']
|
|
|
|
files_local = _get_local_files()
|
|
|
|
outdir = args['outdir']
|
|
|
|
if not outdir:
|
|
print("no outdir specified")
|
|
sys.exit(-1)
|
|
|
|
if not version:
|
|
version = VersionDetector._autodetect(files_local, args["basename"])
|
|
|
|
if not version:
|
|
print("unable to detect the version")
|
|
sys.exit(-1)
|
|
|
|
# if no files explicitly specified process whole directory
|
|
files = args['file'] or files_local
|
|
|
|
# do version convertion if needed
|
|
pack_type = PackageTypeDetector._get_package_type(files)
|
|
version_converted = None
|
|
if pack_type == "python":
|
|
version_converted = _version_python_pip2rpm(version)
|
|
|
|
# handle rpm specs
|
|
for f in filter(lambda x: x.endswith(".spec"), files):
|
|
filename = outdir + "/" + f
|
|
shutil.copyfile(f, filename)
|
|
_replace_define(filename, "version_unconverted", version,
|
|
add_if_missing=False)
|
|
if version_converted and version_converted != version:
|
|
_replace_define(filename, "version_unconverted", version)
|
|
_replace_tag(filename, 'Version', version_converted)
|
|
_replace_spec_setup(filename, "version_unconverted")
|
|
else:
|
|
_replace_tag(filename, 'Version', version)
|
|
_replace_tag(filename, 'Release', "0")
|
|
|
|
# handle debian packages
|
|
# append -0 only for non-native packages, otherwise native packages
|
|
# will be half-converted to non-native and break dpkg-buildpackage
|
|
for f in filter(lambda x: x.endswith(".dsc"), files):
|
|
filename = outdir + "/" + f
|
|
shutil.copyfile(f, filename)
|
|
if "-" in VersionDetector._get_version_via_debian_dsc(filename):
|
|
_replace_tag(filename, 'Version', version + "-0")
|
|
else:
|
|
_replace_tag(filename, 'Version', version)
|
|
|
|
for f in filter(lambda x: x.endswith(("debian.changelog")), files):
|
|
filename = outdir + "/" + f
|
|
shutil.copyfile(f, filename)
|
|
if "-" in VersionDetector._get_version_via_debian_changelog(filename):
|
|
_replace_debian_changelog_version(filename, version + "-0")
|
|
else:
|
|
_replace_debian_changelog_version(filename, version)
|
|
|
|
# handle build.collax recipes
|
|
for f in filter(lambda x: x.endswith(("build.collax")), files):
|
|
filename = outdir + "/" + f
|
|
shutil.copyfile(f, filename)
|
|
_replace_tag(filename, "version", version)
|
|
_replace_tag(filename, "build", "0")
|
|
|
|
# handle arch linux PKGBUILD files
|
|
# TODO: Handle the md5sums generation!
|
|
for f in filter(lambda x: x.endswith(("PKGBUILD")), files):
|
|
filename = outdir + "/" + f
|
|
shutil.copyfile(f, filename)
|
|
_replace_tag(filename, "md5sums", "('SKIP')")
|
|
_replace_tag(filename, "sha256sums", "('SKIP')")
|
|
_replace_tag(filename, "pkgver", version)
|
|
_replace_tag(filename, "pkgrel", "0")
|