diff --git a/lib/spack/external/distro.py b/lib/spack/external/distro.py index 091bba3db8..ca25339ec9 100644 --- a/lib/spack/external/distro.py +++ b/lib/spack/external/distro.py @@ -31,14 +31,16 @@ import os import re import sys +import json import shlex +import logging +import argparse import subprocess if not sys.platform.startswith('linux'): raise ImportError('Unsupported platform: {0}'.format(sys.platform)) - _UNIXCONFDIR = '/etc' _OS_RELEASE_BASENAME = 'os-release' @@ -75,7 +77,6 @@ 'redhat': 'rhel', # RHEL 6.x, 7.x } - # Pattern for content of distro release file (reversed) _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN = re.compile( r'(?:[^)]*\)(.*)\()? *(?:STL )?([\d.+\-a-z]*\d) *(?:esaeler *)?(.+)') @@ -119,7 +120,7 @@ def linux_distribution(full_distribution_name=True): method normalizes the distro ID string to a reliable machine-readable value for a number of popular Linux distributions. """ - return _distroi.linux_distribution(full_distribution_name) + return _distro.linux_distribution(full_distribution_name) def id(): @@ -194,7 +195,7 @@ def id(): command, with ID values that differ from what was previously determined from the distro release file name. """ - return _distroi.id() + return _distro.id() def name(pretty=False): @@ -233,7 +234,7 @@ def name(pretty=False): with the value of the pretty version ("" and "" fields) of the distro release file, if available. """ - return _distroi.name(pretty) + return _distro.name(pretty) def version(pretty=False, best=False): @@ -277,7 +278,7 @@ def version(pretty=False, best=False): the lsb_release command, if it follows the format of the distro release files. """ - return _distroi.version(pretty, best) + return _distro.version(pretty, best) def version_parts(best=False): @@ -294,7 +295,7 @@ def version_parts(best=False): For a description of the *best* parameter, see the :func:`distro.version` method. """ - return _distroi.version_parts(best) + return _distro.version_parts(best) def major_version(best=False): @@ -307,7 +308,7 @@ def major_version(best=False): For a description of the *best* parameter, see the :func:`distro.version` method. """ - return _distroi.major_version(best) + return _distro.major_version(best) def minor_version(best=False): @@ -320,7 +321,7 @@ def minor_version(best=False): For a description of the *best* parameter, see the :func:`distro.version` method. """ - return _distroi.minor_version(best) + return _distro.minor_version(best) def build_number(best=False): @@ -333,7 +334,7 @@ def build_number(best=False): For a description of the *best* parameter, see the :func:`distro.version` method. """ - return _distroi.build_number(best) + return _distro.build_number(best) def like(): @@ -350,7 +351,7 @@ def like(): `os-release man page `_. """ - return _distroi.like() + return _distro.like() def codename(): @@ -374,7 +375,7 @@ def codename(): * the value of the "" field of the distro release file. """ - return _distroi.codename() + return _distro.codename() def info(pretty=False, best=False): @@ -418,7 +419,7 @@ def info(pretty=False, best=False): For a description of the *pretty* and *best* parameters, see the :func:`distro.version` method. """ - return _distroi.info(pretty, best) + return _distro.info(pretty, best) def os_release_info(): @@ -428,7 +429,7 @@ def os_release_info(): See `os-release file`_ for details about these information items. """ - return _distroi.os_release_info() + return _distro.os_release_info() def lsb_release_info(): @@ -439,7 +440,7 @@ def lsb_release_info(): See `lsb_release command output`_ for details about these information items. """ - return _distroi.lsb_release_info() + return _distro.lsb_release_info() def distro_release_info(): @@ -449,7 +450,7 @@ def distro_release_info(): See `distro release file`_ for details about these information items. """ - return _distroi.distro_release_info() + return _distro.distro_release_info() def os_release_attr(attribute): @@ -468,7 +469,7 @@ def os_release_attr(attribute): See `os-release file`_ for details about these information items. """ - return _distroi.os_release_attr(attribute) + return _distro.os_release_attr(attribute) def lsb_release_attr(attribute): @@ -488,7 +489,7 @@ def lsb_release_attr(attribute): See `lsb_release command output`_ for details about these information items. """ - return _distroi.lsb_release_attr(attribute) + return _distro.lsb_release_attr(attribute) def distro_release_attr(attribute): @@ -507,7 +508,7 @@ def distro_release_attr(attribute): See `distro release file`_ for details about these information items. """ - return _distroi.distro_release_attr(attribute) + return _distro.distro_release_attr(attribute) class LinuxDistribution(object): @@ -590,12 +591,14 @@ def __init__(self, self.os_release_file = os_release_file or \ os.path.join(_UNIXCONFDIR, _OS_RELEASE_BASENAME) self.distro_release_file = distro_release_file or '' # updated later - self._os_release_info = self._os_release_info() - self._lsb_release_info = self._lsb_release_info() \ + self._os_release_info = self._get_os_release_info() + self._lsb_release_info = self._get_lsb_release_info() \ if include_lsb else {} - self._distro_release_info = self._distro_release_info() + self._distro_release_info = self._get_distro_release_info() def __repr__(self): + """Return repr of all info + """ return \ "LinuxDistribution(" \ "os_release_file={0!r}, " \ @@ -624,25 +627,25 @@ def linux_distribution(self, full_distribution_name=True): ) def id(self): - """ - Return the distro ID of the Linux distribution, as a string. + """Return the distro ID of the Linux distribution, as a string. For details, see :func:`distro.id`. """ + def normalize(distro_id, table): + distro_id = distro_id.lower().replace(' ', '_') + return table.get(distro_id, distro_id) + distro_id = self.os_release_attr('id') if distro_id: - distro_id = distro_id.lower().replace(' ', '_') - return NORMALIZED_OS_ID.get(distro_id, distro_id) + return normalize(distro_id, NORMALIZED_OS_ID) distro_id = self.lsb_release_attr('distributor_id') if distro_id: - distro_id = distro_id.lower().replace(' ', '_') - return NORMALIZED_LSB_ID.get(distro_id, distro_id) + return normalize(distro_id, NORMALIZED_LSB_ID) distro_id = self.distro_release_attr('id') if distro_id: - distro_id = distro_id.lower().replace(' ', '_') - return NORMALIZED_DISTRO_ID.get(distro_id, distro_id) + return normalize(distro_id, NORMALIZED_DISTRO_ID) return '' @@ -707,10 +710,10 @@ def version_parts(self, best=False): """ version_str = self.version(best=best) if version_str: - g = re.compile(r'(\d+)\.?(\d+)?\.?(\d+)?') - m = g.match(version_str) - if m: - major, minor, build_number = m.groups() + version_regex = re.compile(r'(\d+)\.?(\d+)?\.?(\d+)?') + matches = version_regex.match(version_str) + if matches: + major, minor, build_number = matches.groups() return major, minor or '', build_number or '' return '', '', '' @@ -832,7 +835,7 @@ def distro_release_attr(self, attribute): """ return self._distro_release_info.get(attribute, '') - def _os_release_info(self): + def _get_os_release_info(self): """ Get the information items from the specified os-release file. @@ -840,8 +843,8 @@ def _os_release_info(self): A dictionary containing all information items. """ if os.path.isfile(self.os_release_file): - with open(self.os_release_file, 'r') as f: - return self._parse_os_release_content(f) + with open(self.os_release_file) as release_file: + return self._parse_os_release_content(release_file) return {} @staticmethod @@ -904,7 +907,7 @@ def _parse_os_release_content(lines): pass return props - def _lsb_release_info(self): + def _get_lsb_release_info(self): """ Get the information items from the lsb_release command output. @@ -912,26 +915,26 @@ def _lsb_release_info(self): A dictionary containing all information items. """ cmd = 'lsb_release -a' - p = subprocess.Popen( + process = subprocess.Popen( cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = p.communicate() - out, err = out.decode('ascii'), err.decode('ascii') - rc = p.returncode - if rc == 0: - content = out.splitlines() + stdout, stderr = process.communicate() + stdout, stderr = stdout.decode('utf-8'), stderr.decode('utf-8') + code = process.returncode + if code == 0: + content = stdout.splitlines() return self._parse_lsb_release_content(content) - elif rc == 127: # Command not found + elif code == 127: # Command not found return {} else: if sys.version_info[:2] >= (3, 5): - raise subprocess.CalledProcessError(rc, cmd, out, err) + raise subprocess.CalledProcessError(code, cmd, stdout, stderr) elif sys.version_info[:2] >= (2, 7): - raise subprocess.CalledProcessError(rc, cmd, out) + raise subprocess.CalledProcessError(code, cmd, stdout) elif sys.version_info[:2] == (2, 6): - raise subprocess.CalledProcessError(rc, cmd) + raise subprocess.CalledProcessError(code, cmd) @staticmethod def _parse_lsb_release_content(lines): @@ -949,8 +952,7 @@ def _parse_lsb_release_content(lines): """ props = {} for line in lines: - if isinstance(line, bytes): - line = line.decode('utf-8') + line = line.decode('utf-8') if isinstance(line, bytes) else line kv = line.strip('\n').split(':', 1) if len(kv) != 2: # Ignore lines without colon. @@ -959,7 +961,7 @@ def _parse_lsb_release_content(lines): props.update({k.replace(' ', '_').lower(): v.strip()}) return props - def _distro_release_info(self): + def _get_distro_release_info(self): """ Get the information items from the specified distro release file. @@ -1012,7 +1014,7 @@ def _parse_distro_release_file(self, filepath): A dictionary containing all information items. """ if os.path.isfile(filepath): - with open(filepath, 'r') as fp: + with open(filepath) as fp: # Only parse the first line. For instance, on SLES there # are multiple lines. We don't want them... return self._parse_distro_release_content(fp.readline()) @@ -1032,18 +1034,48 @@ def _parse_distro_release_content(line): """ if isinstance(line, bytes): line = line.decode('utf-8') - m = _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN.match( + matches = _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN.match( line.strip()[::-1]) distro_info = {} - if m: - distro_info['name'] = m.group(3)[::-1] # regexp ensures non-None - if m.group(2): - distro_info['version_id'] = m.group(2)[::-1] - if m.group(1): - distro_info['codename'] = m.group(1)[::-1] + if matches: + # regexp ensures non-None + distro_info['name'] = matches.group(3)[::-1] + if matches.group(2): + distro_info['version_id'] = matches.group(2)[::-1] + if matches.group(1): + distro_info['codename'] = matches.group(1)[::-1] elif line: distro_info['name'] = line.strip() return distro_info -_distroi = LinuxDistribution() +_distro = LinuxDistribution() + + +def main(): + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + logger.addHandler(logging.StreamHandler(sys.stdout)) + + parser = argparse.ArgumentParser(description="Linux distro info tool") + parser.add_argument( + '--json', + '-j', + help="Output in machine readable format", + action="store_true") + args = parser.parse_args() + + if args.json: + logger.info(json.dumps(info(), indent=4, sort_keys=True)) + else: + logger.info('Name: %s', name(pretty=True)) + distribution_version = version(pretty=True) + if distribution_version: + logger.info('Version: %s', distribution_version) + distribution_codename = codename() + if distribution_codename: + logger.info('Codename: %s', distribution_codename) + + +if __name__ == '__main__': + main()