Source code for crl.devutils.versionhandler

import os
import re
import time
import errno
from contextlib import contextmanager
from crl.devutils import utils


__copyright__ = 'Copyright (C) 2019, Nokia'


[docs]class VersionFileNotFound(Exception): pass
[docs]class MultipleVersionFilesFound(Exception): pass
[docs]class FailedToCreateVersionFile(Exception): pass
[docs]class FailedToWriteVersion(Exception): pass
[docs]class FailedToWriteGithash(Exception): pass
[docs]class InvalidVersionValue(Exception): pass
[docs]class VersionHandler(object): """ Version handler for ./src/crl/<libname>/_version.py`. Version can have these values: - Actual version number to use. See below for supported formats. - String 'dev' to update version to latest development version (e.g. 2.8 -> 2.8.1.dev, 2.8.1 -> 2.8.2.dev, 2.8a1 -> 2.8.dev) with the current date added or updated. Given version must be in one of these PEP-440 compatible formats: - Stable version in 'X.Y' or 'X.Y.Z' format (e.g. 2.8, 2.8.6) - Pre-releases with 'aN', 'bN' or 'rcN' postfix (e.g. 2.8a1, 2.8.6rc2) - Development releases with '.devYYYYMMDD' postfix (e.g. 2.8.6.dev20141001) or with '.dev' alone (e.g. 2.8.6.dev) in which case date is added automatically. Args: libname: Name of the library. pathtoversionfile: Relative path to the version file. """ VERSION_FILE_TEMPLATE = ("VERSION = '{version}'\n" "GITHASH = ''\n" "\n\n" "def get_version():\n" " return VERSION\n" "\n\n" "def get_githash():\n" " return GITHASH\n") VERSION_RE = re.compile('^((\d+\.\d+)(\.\d+)?)((a|b|rc|.dev)(\d+))?$') def __init__(self, libname=None, pathtoversionfile=None): self.libname = libname self.pathtoversionfile = pathtoversionfile @property def version(self): return self._run_version_file_function('get_version') @property def version_file(self): if self.pathtoversionfile: return self.pathtoversionfile return self._get_version_file() def _run_version_file_function(self, function_name): namespace = {} utils.execfile(self.version_file, namespace) return namespace[function_name]() @property def githash(self): return self._run_version_file_function('get_githash') def set_githash(self, githash): self._update_file(self.version_file, "GITHASH = '.*'", githash) self._verify_githash(githash, FailedToWriteGithash) def _verify_githash(self, githash, exception): if githash != self.githash: raise exception( "Githash should be '{githash}'" " but it is '{selfgithash}'".format( githash=githash, selfgithash=self.githash)) def set_version(self, version): if version != 'keep': self._write_version_file_and_verify( self._validate_and_update(version)) def _validate_and_update(self, version): if version == 'dev': version = self._get_dev_version() if version.endswith('.dev'): version += time.strftime('%Y%m%d') self.validate_version(version) return version def validate_version(self, version): if not self.VERSION_RE.match(version): raise InvalidVersionValue("Invalid version '{}'.".format(version)) def _get_dev_version(self): major, minor, pre = self.VERSION_RE.match(self.version).groups()[1:4] if not pre: minor = '.{}'.format(int(minor[1:]) + 1 if minor else 1) if not minor: minor = '' return '{}{}.dev'.format(major, minor) def _write_version_file_and_verify(self, version): self._write_version_file(version) self._verify_version(version, FailedToWriteVersion) def _verify_version(self, version, exception): if version != self.version: raise exception( "Version should be '{version}'" " but it is '{selfversion}'".format( version=version, selfversion=self.version)) def _write_version_file(self, version): try: with self.__ioerror_handling(): self._update_file(self.version_file, "VERSION = '.*'", version) except VersionFileNotFound: self._create_initial_version_file(version) @staticmethod @contextmanager def __ioerror_handling(): try: yield None except IOError as e: if e.errno != errno.ENOENT: raise FailedToCreateVersionFile(str(e)) raise VersionFileNotFound @staticmethod def _update_file(path, pattern, replacement): replacement = pattern.replace('.*', replacement) with open(path) as version_file: content = ''.join(re.sub(pattern, replacement, line) for line in version_file) with open(path, 'w') as version_file: version_file.write(content) def _create_initial_version_file(self, version): with open(self._get_new_version_file(), 'w') as vsf: vsf.write(self.VERSION_FILE_TEMPLATE.replace('{version}', version)) def _get_version_file(self): return (self._try_to_get_version_file_for_subdir(self.libname) if self.libname else self._try_to_get_version_file()) def _get_new_version_file(self): return (self._try_to_get_version_file_for_subdir(self.libname) if self.libname else self._get_default_version_file()) def _try_to_get_version_file(self): vsf = self._get_all_existing_version_files() vslen = len(vsf) if vslen == 0: raise VersionFileNotFound() if vslen == 1: return vsf[0] raise MultipleVersionFilesFound('Candidates are: {vsf}.'.format( vsf=vsf)) def _get_all_existing_version_files(self): return [v for v in self._get_all_version_files() if os.path.isfile(v)] def _get_all_version_files(self): return [self._get_version_file_for_subdir(n) for n in self._get_crl_subdirs()] def _get_default_version_file(self): return self._get_version_file_for_subdir(self.get_default_lib()) def _try_to_get_version_file_for_subdir(self, subdir): vfile = self._get_version_file_for_subdir(subdir) if os.path.isfile(vfile): return vfile raise VersionFileNotFound() def _get_version_file_for_subdir(self, subdir): return os.path.abspath( os.path.join(self._get_crl_path(), subdir, '_version.py')) def get_default_lib(self): return self._get_crl_subdirs()[0] def _get_crl_subdirs(self): return next(os.walk(self._get_crl_path()))[1] @staticmethod def _get_crl_path(): return os.path.join('src', 'crl')