%PDF- %PDF-
Direktori : /usr/lib/python2.7/site-packages/salt/ |
Current File : //usr/lib/python2.7/site-packages/salt/version.py |
# -*- coding: utf-8 -*- ''' Set up the version of Salt ''' # Import python libs from __future__ import absolute_import, print_function, unicode_literals import re import sys import platform import warnings # linux_distribution deprecated in py3.7 try: from platform import linux_distribution as _deprecated_linux_distribution def linux_distribution(**kwargs): with warnings.catch_warnings(): warnings.simplefilter("ignore") return _deprecated_linux_distribution(**kwargs) except ImportError: from distro import linux_distribution # pylint: disable=invalid-name,redefined-builtin # Import 3rd-party libs from salt.ext import six from salt.ext.six.moves import map # Don't rely on external packages in this module since it's used at install time if sys.version_info[0] == 3: MAX_SIZE = sys.maxsize string_types = (str,) else: MAX_SIZE = sys.maxint string_types = (six.string_types,) # pylint: enable=invalid-name,redefined-builtin # ----- ATTENTION ---------------------------------------------------------------------------------------------------> # # ALL major version bumps, new release codenames, MUST be defined in the SaltStackVersion.NAMES dictionary, i.e.: # # class SaltStackVersion(object): # # NAMES = { # 'Hydrogen': (2014, 1), # <- This is the tuple to bump versions # ( ... ) # } # # # ONLY UPDATE CODENAMES AFTER BRANCHING # # As an example, The Helium codename must only be properly defined with "(2014, 7)" after Hydrogen, "(2014, 1)", has # been branched out into it's own branch. # # ALL OTHER VERSION INFORMATION IS EXTRACTED FROM THE GIT TAGS # # <---- ATTENTION ---------------------------------------------------------------------------------------------------- class SaltStackVersion(object): ''' Handle SaltStack versions class. Knows how to parse ``git describe`` output, knows about release candidates and also supports version comparison. ''' __slots__ = ('name', 'major', 'minor', 'bugfix', 'mbugfix', 'pre_type', 'pre_num', 'noc', 'sha') git_describe_regex = re.compile( r'(?:[^\d]+)?(?P<major>[\d]{1,4})' r'\.(?P<minor>[\d]{1,2})' r'(?:\.(?P<bugfix>[\d]{0,2}))?' r'(?:\.(?P<mbugfix>[\d]{0,2}))?' r'(?:(?P<pre_type>rc|a|b|alpha|beta|nb)(?P<pre_num>[\d]{1}))?' r'(?:(?:.*)-(?P<noc>(?:[\d]+|n/a))-(?P<sha>[a-z0-9]{8}))?' ) git_sha_regex = r'(?P<sha>[a-z0-9]{7})' if six.PY2: git_sha_regex = git_sha_regex.decode(__salt_system_encoding__) git_sha_regex = re.compile(git_sha_regex) # Salt versions after 0.17.0 will be numbered like: # <4-digit-year>.<month>.<bugfix> # # Since the actual version numbers will only be know on release dates, the # periodic table element names will be what's going to be used to name # versions and to be able to mention them. NAMES = { # Let's keep at least 3 version names uncommented counting from the # latest release so we can map deprecation warnings to versions. # pylint: disable=E8203 # ----- Please refrain from fixing PEP-8 E203 and E265 -----> # The idea is to keep this readable. # ----------------------------------------------------------- 'Hydrogen' : (2014, 1), 'Helium' : (2014, 7), 'Lithium' : (2015, 5), 'Beryllium' : (2015, 8), 'Boron' : (2016, 3), 'Carbon' : (2016, 11), 'Nitrogen' : (2017, 7), 'Oxygen' : (2018, 3), 'Fluorine' : (2019, 2), 'Neon' : (MAX_SIZE - 99, 0), 'Sodium' : (MAX_SIZE - 98, 0), 'Magnesium' : (MAX_SIZE - 97, 0), # pylint: disable=E8265 #'Aluminium' : (MAX_SIZE - 96, 0), #'Silicon' : (MAX_SIZE - 95, 0), #'Phosphorus' : (MAX_SIZE - 94, 0), #'Sulfur' : (MAX_SIZE - 93, 0), #'Chlorine' : (MAX_SIZE - 92, 0), #'Argon' : (MAX_SIZE - 91, 0), #'Potassium' : (MAX_SIZE - 90, 0), #'Calcium' : (MAX_SIZE - 89, 0), #'Scandium' : (MAX_SIZE - 88, 0), #'Titanium' : (MAX_SIZE - 87, 0), #'Vanadium' : (MAX_SIZE - 86, 0), #'Chromium' : (MAX_SIZE - 85, 0), #'Manganese' : (MAX_SIZE - 84, 0), #'Iron' : (MAX_SIZE - 83, 0), #'Cobalt' : (MAX_SIZE - 82, 0), #'Nickel' : (MAX_SIZE - 81, 0), #'Copper' : (MAX_SIZE - 80, 0), #'Zinc' : (MAX_SIZE - 79, 0), #'Gallium' : (MAX_SIZE - 78, 0), #'Germanium' : (MAX_SIZE - 77, 0), #'Arsenic' : (MAX_SIZE - 76, 0), #'Selenium' : (MAX_SIZE - 75, 0), #'Bromine' : (MAX_SIZE - 74, 0), #'Krypton' : (MAX_SIZE - 73, 0), #'Rubidium' : (MAX_SIZE - 72, 0), #'Strontium' : (MAX_SIZE - 71, 0), #'Yttrium' : (MAX_SIZE - 70, 0), #'Zirconium' : (MAX_SIZE - 69, 0), #'Niobium' : (MAX_SIZE - 68, 0), #'Molybdenum' : (MAX_SIZE - 67, 0), #'Technetium' : (MAX_SIZE - 66, 0), #'Ruthenium' : (MAX_SIZE - 65, 0), #'Rhodium' : (MAX_SIZE - 64, 0), #'Palladium' : (MAX_SIZE - 63, 0), #'Silver' : (MAX_SIZE - 62, 0), #'Cadmium' : (MAX_SIZE - 61, 0), #'Indium' : (MAX_SIZE - 60, 0), #'Tin' : (MAX_SIZE - 59, 0), #'Antimony' : (MAX_SIZE - 58, 0), #'Tellurium' : (MAX_SIZE - 57, 0), #'Iodine' : (MAX_SIZE - 56, 0), #'Xenon' : (MAX_SIZE - 55, 0), #'Caesium' : (MAX_SIZE - 54, 0), #'Barium' : (MAX_SIZE - 53, 0), #'Lanthanum' : (MAX_SIZE - 52, 0), #'Cerium' : (MAX_SIZE - 51, 0), #'Praseodymium' : (MAX_SIZE - 50, 0), #'Neodymium' : (MAX_SIZE - 49, 0), #'Promethium' : (MAX_SIZE - 48, 0), #'Samarium' : (MAX_SIZE - 47, 0), #'Europium' : (MAX_SIZE - 46, 0), #'Gadolinium' : (MAX_SIZE - 45, 0), #'Terbium' : (MAX_SIZE - 44, 0), #'Dysprosium' : (MAX_SIZE - 43, 0), #'Holmium' : (MAX_SIZE - 42, 0), #'Erbium' : (MAX_SIZE - 41, 0), #'Thulium' : (MAX_SIZE - 40, 0), #'Ytterbium' : (MAX_SIZE - 39, 0), #'Lutetium' : (MAX_SIZE - 38, 0), #'Hafnium' : (MAX_SIZE - 37, 0), #'Tantalum' : (MAX_SIZE - 36, 0), #'Tungsten' : (MAX_SIZE - 35, 0), #'Rhenium' : (MAX_SIZE - 34, 0), #'Osmium' : (MAX_SIZE - 33, 0), #'Iridium' : (MAX_SIZE - 32, 0), #'Platinum' : (MAX_SIZE - 31, 0), #'Gold' : (MAX_SIZE - 30, 0), #'Mercury' : (MAX_SIZE - 29, 0), #'Thallium' : (MAX_SIZE - 28, 0), #'Lead' : (MAX_SIZE - 27, 0), #'Bismuth' : (MAX_SIZE - 26, 0), #'Polonium' : (MAX_SIZE - 25, 0), #'Astatine' : (MAX_SIZE - 24, 0), #'Radon' : (MAX_SIZE - 23, 0), #'Francium' : (MAX_SIZE - 22, 0), #'Radium' : (MAX_SIZE - 21, 0), #'Actinium' : (MAX_SIZE - 20, 0), #'Thorium' : (MAX_SIZE - 19, 0), #'Protactinium' : (MAX_SIZE - 18, 0), #'Uranium' : (MAX_SIZE - 17, 0), #'Neptunium' : (MAX_SIZE - 16, 0), #'Plutonium' : (MAX_SIZE - 15, 0), #'Americium' : (MAX_SIZE - 14, 0), #'Curium' : (MAX_SIZE - 13, 0), #'Berkelium' : (MAX_SIZE - 12, 0), #'Californium' : (MAX_SIZE - 11, 0), #'Einsteinium' : (MAX_SIZE - 10, 0), #'Fermium' : (MAX_SIZE - 9, 0), #'Mendelevium' : (MAX_SIZE - 8, 0), #'Nobelium' : (MAX_SIZE - 7, 0), #'Lawrencium' : (MAX_SIZE - 6, 0), #'Rutherfordium': (MAX_SIZE - 5, 0), #'Dubnium' : (MAX_SIZE - 4, 0), #'Seaborgium' : (MAX_SIZE - 3, 0), #'Bohrium' : (MAX_SIZE - 2, 0), #'Hassium' : (MAX_SIZE - 1, 0), #'Meitnerium' : (MAX_SIZE - 0, 0), # <---- Please refrain from fixing PEP-8 E203 and E265 ------ # pylint: enable=E8203,E8265 } LNAMES = dict((k.lower(), v) for (k, v) in iter(NAMES.items())) VNAMES = dict((v, k) for (k, v) in iter(NAMES.items())) RMATCH = dict((v[:2], k) for (k, v) in iter(NAMES.items())) def __init__(self, # pylint: disable=C0103 major, minor, bugfix=0, mbugfix=0, pre_type=None, pre_num=None, noc=0, sha=None): if isinstance(major, string_types): major = int(major) if isinstance(minor, string_types): minor = int(minor) if bugfix is None: bugfix = 0 elif isinstance(bugfix, string_types): bugfix = int(bugfix) if mbugfix is None: mbugfix = 0 elif isinstance(mbugfix, string_types): mbugfix = int(mbugfix) if pre_type is None: pre_type = '' if pre_num is None: pre_num = 0 elif isinstance(pre_num, string_types): pre_num = int(pre_num) if noc is None: noc = 0 elif isinstance(noc, string_types) and noc == 'n/a': noc = -1 elif isinstance(noc, string_types): noc = int(noc) self.major = major self.minor = minor self.bugfix = bugfix self.mbugfix = mbugfix self.pre_type = pre_type self.pre_num = pre_num self.name = self.VNAMES.get((major, minor), None) self.noc = noc self.sha = sha @classmethod def parse(cls, version_string): if version_string.lower() in cls.LNAMES: return cls.from_name(version_string) vstr = version_string.decode() if isinstance(version_string, bytes) else version_string match = cls.git_describe_regex.match(vstr) if not match: raise ValueError( 'Unable to parse version string: \'{0}\''.format(version_string) ) return cls(*match.groups()) @classmethod def from_name(cls, name): if name.lower() not in cls.LNAMES: raise ValueError( 'Named version \'{0}\' is not known'.format(name) ) return cls(*cls.LNAMES[name.lower()]) @classmethod def from_last_named_version(cls): return cls.from_name( cls.VNAMES[ max([version_info for version_info in cls.VNAMES if version_info[0] < (MAX_SIZE - 200)]) ] ) @classmethod def next_release(cls): return cls.from_name( cls.VNAMES[ min([version_info for version_info in cls.VNAMES if version_info > cls.from_last_named_version().info]) ] ) @property def sse(self): # Higher than 0.17, lower than first date based return 0 < self.major < 2014 @property def info(self): return ( self.major, self.minor, self.bugfix, self.mbugfix ) @property def pre_info(self): return ( self.major, self.minor, self.bugfix, self.mbugfix, self.pre_type, self.pre_num ) @property def noc_info(self): return ( self.major, self.minor, self.bugfix, self.mbugfix, self.pre_type, self.pre_num, self.noc ) @property def full_info(self): return ( self.major, self.minor, self.bugfix, self.mbugfix, self.pre_type, self.pre_num, self.noc, self.sha ) @property def string(self): version_string = '{0}.{1}.{2}'.format( self.major, self.minor, self.bugfix ) if self.mbugfix: version_string += '.{0}'.format(self.mbugfix) if self.pre_type: version_string += '{0}{1}'.format(self.pre_type, self.pre_num) if self.noc and self.sha: noc = self.noc if noc < 0: noc = 'n/a' version_string += '-{0}-{1}'.format(noc, self.sha) return version_string @property def formatted_version(self): if self.name and self.major > 10000: version_string = self.name if self.sse: version_string += ' Enterprise' version_string += ' (Unreleased)' return version_string version_string = self.string if self.sse: version_string += ' Enterprise' if (self.major, self.minor) in self.RMATCH: version_string += ' ({0})'.format(self.RMATCH[(self.major, self.minor)]) return version_string def __str__(self): return self.string def __compare__(self, other, method): if not isinstance(other, SaltStackVersion): if isinstance(other, string_types): other = SaltStackVersion.parse(other) elif isinstance(other, (list, tuple)): other = SaltStackVersion(*other) else: raise ValueError( 'Cannot instantiate Version from type \'{0}\''.format( type(other) ) ) if (self.pre_type and other.pre_type) or (not self.pre_type and not other.pre_type): # Both either have or don't have pre-release information, regular compare is ok return method(self.noc_info, other.noc_info) if self.pre_type and not other.pre_type: # We have pre-release information, the other side doesn't other_noc_info = list(other.noc_info) other_noc_info[4] = 'zzzzz' return method(self.noc_info, tuple(other_noc_info)) if not self.pre_type and other.pre_type: # The other side has pre-release informatio, we don't noc_info = list(self.noc_info) noc_info[4] = 'zzzzz' return method(tuple(noc_info), other.noc_info) def __lt__(self, other): return self.__compare__(other, lambda _self, _other: _self < _other) def __le__(self, other): return self.__compare__(other, lambda _self, _other: _self <= _other) def __eq__(self, other): return self.__compare__(other, lambda _self, _other: _self == _other) def __ne__(self, other): return self.__compare__(other, lambda _self, _other: _self != _other) def __ge__(self, other): return self.__compare__(other, lambda _self, _other: _self >= _other) def __gt__(self, other): return self.__compare__(other, lambda _self, _other: _self > _other) def __repr__(self): parts = [] if self.name: parts.append('name=\'{0}\''.format(self.name)) parts.extend([ 'major={0}'.format(self.major), 'minor={0}'.format(self.minor), 'bugfix={0}'.format(self.bugfix) ]) if self.mbugfix: parts.append('minor-bugfix={0}'.format(self.mbugfix)) if self.pre_type: parts.append('{0}={1}'.format(self.pre_type, self.pre_num)) noc = self.noc if noc == -1: noc = 'n/a' if noc and self.sha: parts.extend([ 'noc={0}'.format(noc), 'sha={0}'.format(self.sha) ]) return '<{0} {1}>'.format(self.__class__.__name__, ' '.join(parts)) # ----- Hardcoded Salt Codename Version Information -----------------------------------------------------------------> # # There's no need to do anything here. The last released codename will be picked up # -------------------------------------------------------------------------------------------------------------------- __saltstack_version__ = SaltStackVersion.from_last_named_version() # <---- Hardcoded Salt Version Information --------------------------------------------------------------------------- # ----- Dynamic/Runtime Salt Version Information --------------------------------------------------------------------> def __discover_version(saltstack_version): # This might be a 'python setup.py develop' installation type. Let's # discover the version information at runtime. import os import subprocess if 'SETUP_DIRNAME' in globals(): # This is from the exec() call in Salt's setup.py cwd = SETUP_DIRNAME # pylint: disable=E0602 if not os.path.exists(os.path.join(cwd, '.git')): # This is not a Salt git checkout!!! Don't even try to parse... return saltstack_version else: cwd = os.path.abspath(os.path.dirname(__file__)) if not os.path.exists(os.path.join(os.path.dirname(cwd), '.git')): # This is not a Salt git checkout!!! Don't even try to parse... return saltstack_version try: kwargs = dict( stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd ) if not sys.platform.startswith('win'): # Let's not import `salt.utils` for the above check kwargs['close_fds'] = True process = subprocess.Popen( ['git', 'describe', '--tags', '--first-parent', '--match', 'v[0-9]*', '--always'], **kwargs) out, err = process.communicate() if process.returncode != 0: # The git version running this might not support --first-parent # Revert to old command process = subprocess.Popen( ['git', 'describe', '--tags', '--match', 'v[0-9]*', '--always'], **kwargs) out, err = process.communicate() if six.PY3: out = out.decode() err = err.decode() out = out.strip() err = err.strip() if not out or err: return saltstack_version try: return SaltStackVersion.parse(out) except ValueError: if not SaltStackVersion.git_sha_regex.match(out): raise # We only define the parsed SHA and set NOC as ??? (unknown) saltstack_version.sha = out.strip() saltstack_version.noc = -1 except OSError as os_err: if os_err.errno != 2: # If the errno is not 2(The system cannot find the file # specified), raise the exception so it can be catch by the # developers raise return saltstack_version def __get_version(saltstack_version): ''' If we can get a version provided at installation time or from Git, use that instead, otherwise we carry on. ''' try: # Try to import the version information provided at install time from salt._version import __saltstack_version__ # pylint: disable=E0611,F0401 return __saltstack_version__ except ImportError: return __discover_version(saltstack_version) # Get additional version information if available __saltstack_version__ = __get_version(__saltstack_version__) # This function has executed once, we're done with it. Delete it! del __get_version # <---- Dynamic/Runtime Salt Version Information --------------------------------------------------------------------- # ----- Common version related attributes - NO NEED TO CHANGE -------------------------------------------------------> __version_info__ = __saltstack_version__.info __version__ = __saltstack_version__.string # <---- Common version related attributes - NO NEED TO CHANGE -------------------------------------------------------- def salt_information(): ''' Report version of salt. ''' yield 'Salt', __version__ def dependency_information(include_salt_cloud=False): ''' Report versions of library dependencies. ''' libs = [ ('Python', None, sys.version.rsplit('\n')[0].strip()), ('Jinja2', 'jinja2', '__version__'), ('M2Crypto', 'M2Crypto', 'version'), ('msgpack-python', 'msgpack', 'version'), ('msgpack-pure', 'msgpack_pure', 'version'), ('pycrypto', 'Crypto', '__version__'), ('pycryptodome', 'Cryptodome', 'version_info'), ('libnacl', 'libnacl', '__version__'), ('PyYAML', 'yaml', '__version__'), ('ioflo', 'ioflo', '__version__'), ('PyZMQ', 'zmq', '__version__'), ('RAET', 'raet', '__version__'), ('ZMQ', 'zmq', 'zmq_version'), ('Mako', 'mako', '__version__'), ('Tornado', 'tornado', 'version'), ('timelib', 'timelib', 'version'), ('dateutil', 'dateutil', '__version__'), ('pygit2', 'pygit2', '__version__'), ('libgit2', 'pygit2', 'LIBGIT2_VERSION'), ('smmap', 'smmap', '__version__'), ('cffi', 'cffi', '__version__'), ('pycparser', 'pycparser', '__version__'), ('gitdb', 'gitdb', '__version__'), ('gitpython', 'git', '__version__'), ('python-gnupg', 'gnupg', '__version__'), ('mysql-python', 'MySQLdb', '__version__'), ('cherrypy', 'cherrypy', '__version__'), ('docker-py', 'docker', '__version__'), ] if include_salt_cloud: libs.append( ('Apache Libcloud', 'libcloud', '__version__'), ) for name, imp, attr in libs: if imp is None: yield name, attr continue try: imp = __import__(imp) version = getattr(imp, attr) if callable(version): version = version() if isinstance(version, (tuple, list)): version = '.'.join(map(str, version)) yield name, version except Exception: yield name, None def system_information(): ''' Report system versions. ''' def system_version(): ''' Return host system version. ''' lin_ver = linux_distribution() mac_ver = platform.mac_ver() win_ver = platform.win32_ver() if lin_ver[0]: return ' '.join(lin_ver) elif mac_ver[0]: if isinstance(mac_ver[1], (tuple, list)) and ''.join(mac_ver[1]): return ' '.join([mac_ver[0], '.'.join(mac_ver[1]), mac_ver[2]]) else: return ' '.join([mac_ver[0], mac_ver[2]]) elif win_ver[0]: return ' '.join(win_ver) else: return '' if platform.win32_ver()[0]: # Get the version and release info based on the Windows Operating # System Product Name. As long as Microsoft maintains a similar format # this should be future proof import win32api # pylint: disable=3rd-party-module-not-gated import win32con # pylint: disable=3rd-party-module-not-gated # Get the product name from the registry hkey = win32con.HKEY_LOCAL_MACHINE key = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion' value_name = 'ProductName' reg_handle = win32api.RegOpenKey(hkey, key) # Returns a tuple of (product_name, value_type) product_name, _ = win32api.RegQueryValueEx(reg_handle, value_name) version = 'Unknown' release = '' if 'Server' in product_name: for item in product_name.split(' '): # If it's all digits, then it's version if re.match(r'\d+', item): version = item # If it starts with R and then numbers, it's the release # ie: R2 if re.match(r'^R\d+$', item): release = item release = '{0}Server{1}'.format(version, release) else: for item in product_name.split(' '): # If it's a number, decimal number, Thin or Vista, then it's the # version if re.match(r'^(\d+(\.\d+)?)|Thin|Vista$', item): version = item release = version _, ver, sp, extra = platform.win32_ver() version = ' '.join([release, ver, sp, extra]) else: version = system_version() release = platform.release() system = [ ('system', platform.system()), ('dist', ' '.join(linux_distribution(full_distribution_name=False))), ('release', release), ('machine', platform.machine()), ('version', version), ('locale', __salt_system_encoding__), ] for name, attr in system: yield name, attr continue def versions_information(include_salt_cloud=False): ''' Report the versions of dependent software. ''' salt_info = list(salt_information()) lib_info = list(dependency_information(include_salt_cloud)) sys_info = list(system_information()) return {'Salt Version': dict(salt_info), 'Dependency Versions': dict(lib_info), 'System Versions': dict(sys_info)} def versions_report(include_salt_cloud=False): ''' Yield each version properly formatted for console output. ''' ver_info = versions_information(include_salt_cloud) lib_pad = max(len(name) for name in ver_info['Dependency Versions']) sys_pad = max(len(name) for name in ver_info['System Versions']) padding = max(lib_pad, sys_pad) + 1 fmt = '{0:>{pad}}: {1}' info = [] for ver_type in ('Salt Version', 'Dependency Versions', 'System Versions'): info.append('{0}:'.format(ver_type)) # List dependencies in alphabetical, case insensitive order for name in sorted(ver_info[ver_type], key=lambda x: x.lower()): ver = fmt.format(name, ver_info[ver_type][name] or 'Not Installed', pad=padding) info.append(ver) info.append(' ') for line in info: yield line def msi_conformant_version(): ''' An msi installer uninstalls/replaces a lower "internal version" of itself. "internal version" is ivMAJOR.ivMINOR.ivBUILD with max values 255.255.65535. Using the build nr allows continuous integration of the installer. "Display version" is indipendent and free format: Year.Month.Bugfix as in Salt 2016.11.3. Calculation of the internal version fields: ivMAJOR = 'short year' (2 digits). ivMINOR = 20*(month-1) + Bugfix Combine Month and Bugfix to free ivBUILD for the build number This limits Bugfix < 20. The msi automatically replaces only 19 bugfixes of a month, one must uninstall manually. ivBUILD = git commit count (noc) noc for tags is 0, representing the final word, translates to the highest build number (65535). Examples: git checkout Display version Internal version Remark develop 2016.11.0-742 16.200.742 The develop branch has bugfix 0 2016.11 2016.11.2-78 16.202.78 2016.11 2016.11.9-88 16.209.88 2018.8 2018.3.2-1306 18.42.1306 v2016.11.0 2016.11.0 16.200.65535 Tags have noc 0 v2016.11.2 2016.11.2 16.202.65535 ''' short_year = int(six.text_type(__saltstack_version__.major)[2:]) month = __saltstack_version__.minor bugfix = __saltstack_version__.bugfix if bugfix > 19: bugfix = 19 noc = __saltstack_version__.noc if noc == 0: noc = 65535 return '{}.{}.{}'.format(short_year, 20*(month-1)+bugfix, noc) if __name__ == '__main__': if len(sys.argv) == 2 and sys.argv[1] == 'msi': # Building the msi requires an msi-conformant version print(msi_conformant_version()) else: print(__version__)