ver.py 8.66 KB
Newer Older
Alberto Gonzalez's avatar
Alberto Gonzalez committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223

from __future__ import print_function

import platform
import re
import os.path
import plistlib
import subprocess
import functools


import pyhhi.build.common.util as util
import pyhhi.build.common.cmbldver as cmbldver


def get_cmake_build_version():
    return version_tuple_from_str(cmbldver.CMAKE_BUILD_VERSION_STR)


def _get_python_version_numeric():
    re_number = re.compile(r'(\d+).*')
    version_list = []
    # extra logic to deal with python on ubuntu 11.10: 2.7.2+
    for x in platform.python_version_tuple():
        re_match = re_number.match(x)
        if re_match:
            version_list.append(int(re_match.group(1), 10))
        else:
            version_list.append(0)
    return tuple(version_list)


def check_python_version(version_tuple=None):
    """Checks the python version and throws an exception if the version is not supported."""
    if version_tuple is None:
        version_tuple = _get_python_version_numeric()
    if version_compare(version_tuple, (2, 7)) < 0:
        raise Exception('python ' + platform.python_version() + ' is not supported. Please update to 2.7 or higher.')


def get_python_version(check_version=False):
    version_tuple = _get_python_version_numeric()
    if check_version:
        check_python_version(version_tuple)
    return version_tuple


def version_tuple_from_str(version, nelem=None):
    """Split a version string using '.' and '-' as separators and return a tuple of integers."""
    re_match = re.match(r'([0-9.,_-]+\d+)|(\d+)', version)
    if re_match:
        version_list = re.split('[.,_-]', re_match.group(0))
        if nelem is not None:
            # adjust the list to contain exactly the specified number of elements.
            while len(version_list) < nelem:
                version_list.append('0')
            return tuple([int(x, 10) for x in version_list[:nelem]])
        else:
            return tuple([int(x, 10) for x in version_list])
    else:
        raise Exception("The version string '" + version + "' is not supported, no leading numeric digits found.")


def get_boost_version_str(version_str):
    re_match = re.match(r'([0-9.]+\d+)', version_str)
    if not re_match:
        raise Exception("The version string '" + version_str + "' cannot be converted into a boost compliant version string.")
    boost_version_str = re.sub(r'\.', '_', re_match.group(1))
    return boost_version_str


def version_tuple_to_str(version, sep='.'):
    """Join the version components using '.' and return the string."""
    return sep.join([str(x) for x in version])


def version_list_to_str(version_list):
    return ' '.join([version_tuple_to_str(x) for x in version_list])


def ubuntu_version_tuple_to_str(version):
    """Join the first two version components of an ubuntu release number and return the string."""
    assert len(version) >= 2
    version_str = '%d.%02d' % (version[0], version[1])
    return version_str


def version_compare(version1, version2):
    """Compare two version iterables consisting of arbitrary number of numeric elements."""
    len_version1 = len(version1)
    len_version2 = len(version2)
    if len_version1 == len_version2:
        # Both version objects have the same number of components, compare them left to right
        for i in range(len_version1):
            if version1[i] > version2[i]: return 1
            elif version1[i] < version2[i]: return -1
        # version objects compare equal
        return 0
    elif len_version1 > len_version2:
        version2_tmp = list(version2)
        while len(version2_tmp) < len_version1: version2_tmp.append(0)
        return version_compare(version1, version2_tmp)
    else:
        version1_tmp = list(version1)
        while len(version1_tmp) < len_version2: version1_tmp.append(0)
        return version_compare(version1_tmp, version2)


def version_list_sort(version_list):

    # Written for Python 2.7 and 3.x: functools.cmp_to_key() requires 2.7 or higher.

    # Notes: python 2.x supports a second argument cmp to specify the comparision function but
    #        python 3.x does not.
    return sorted(version_list, key=functools.cmp_to_key(version_compare))


def version_str_to_rpm_version_tuple(version):
    re_match = re.match(r'([^-]+)-(\S+)', version)
    if re_match:
        return re_match.group(1), re_match.group(2)
    else:
        re_match = re.match(r'([\d.-]+)[-.](\d+)', version)
        if re_match:
            return re_match.group(1), re_match.group(2)
    raise Exception("The version string '" + version + "' is not a valid RPM version string.")


def get_default_version_filename(src_filename):
    """Returns the default version filename given a source filename."""
    repo_name = util.find_repo_name_from_src_path(src_filename)
    repo_path = util.find_repo_path_from_src_path(src_filename)
    return os.path.join(repo_path, 'include', repo_name, 'version.h')


def parse_version_file(version_file, verbatim=False):
    """Parse standard version file and return the version ID as a numeric tuple or verbatim."""
    if not os.path.exists(version_file):
        raise Exception("version file '" + version_file + "' does not exist.")

    # look at the extension to determine the file type: header file or plist file.
    (root, ext) = os.path.splitext(version_file)
    if ext == '.plist':
        return _parse_version_plist_file(version_file, verbatim)
    else:
        return _parse_version_h_file(version_file, verbatim)


def _parse_version_h_file(version_file, verbatim=False):
    """Parse standard version file and return the version ID as a numeric tuple or verbatim."""
    if not os.path.exists(version_file):
        raise Exception("version file '" + version_file + "' does not exist.")

    re_version_expr = re.compile(r'(^#if\s+![ ]*defined\(.*)|(^#\s*define\s+\S+_VERSION\s+)')
    re_version_tag_expr = re.compile(r'^#if\s+!\s*defined\(\s*([a-zA-Z0-9_]+)\s*\)')

    version_tag = None
    with open(version_file) as f:
        for line in f:
            re_match = re_version_expr.match(line)
            if not re_match:
                continue
            # print("version.h: found cpp line: {}".format(line))
            if version_tag is None:
                re_match = re_version_tag_expr.match(line)
                if re_match:
                    version_tag = re_match.group(1)
                    # print("found version tag: {}".format(version_tag))
                    re_version_str = re.compile(r'^#define\s+{}\s+"([^"]+)'.format(version_tag))
                    re_version_str2 = re.compile(r'^#define\s+{}\s+(\d+)'.format(version_tag))
                continue
            else:
                # print("checking cpp line: {}".format(line))
                # version tag found
                re_match = re_version_str.match(line)
                if not re_match:
                    re_match = re_version_str2.match(line)
                if re_match:
                    if verbatim:
                        return re_match.group(1)
                    else:
                        return version_tuple_from_str(re_match.group(1))

    raise Exception("No version ID found in file '" + version_file + "'.")


def _parse_version_plist_file(plist_file, verbatim=False):
    """Parse Info.plist file and return the version ID as a numeric tuple or verbatim."""
    if not os.path.exists(plist_file):
        raise Exception("version file '" + plist_file + "' does not exist.")

    version_str = None

    if (platform.system().lower() == 'darwin') and _is_binary_plist_file(plist_file):
        # On MacOSX there are two different plist formats supported: the older xml format and on more recent releases
        # the binary format. The latter is not supported by Python's plistlib directly.
        retv = subprocess.check_output(['/usr/bin/plutil', '-convert', 'xml1', '-o', '-', plist_file], universal_newlines=True)
        pl_dict = plistlib.readPlistFromString(retv)
    else:
        pl_dict = plistlib.readPlist(plist_file)

    # An Info.plist file is expected to contain at least one of the following version keys: CFBundleVersion and CFBundleShortVersionString.
    if 'CFBundleShortVersionString' in pl_dict:
        version_str = pl_dict['CFBundleShortVersionString']
    elif 'CFBundleVersion' in pl_dict:
        version_str = pl_dict['CFBundleVersion']
    else:
        raise Exception("No version ID found in file '" + plist_file + "'.")
    if verbatim:
        return version_str
    else:
        return version_tuple_from_str(version_str)


def _is_binary_plist_file(plist_file):
    if not os.path.exists(plist_file):
        raise Exception("file '" + plist_file + "' does not exist.")
    retv = subprocess.check_output(['/usr/bin/file', plist_file], universal_newlines=True)
    if re.search('binary property list', retv):
        return True
    else:
        return False