Index: Misc/NEWS =================================================================== --- Misc/NEWS (revision 60977) +++ Misc/NEWS (working copy) @@ -434,6 +434,8 @@ Library ------- +- Issue #1322: platform.dist() has unpredictable result under Linux + - Issue 1781: ConfigParser now does not let you add the "default" section (ignore-case) Index: Doc/library/platform.rst =================================================================== --- Doc/library/platform.rst (revision 60977) +++ Doc/library/platform.rst (working copy) @@ -230,13 +230,21 @@ -------------- -.. function:: dist(distname='', version='', id='', supported_dists=('SuSE','debian','redhat','mandrake')) +.. function:: dist(distname='', version='', id='') + + Deprecated by *linux_distribution*. Exist to maintain backwards compatibility. Tries to determine the name of the OS distribution name Returns a tuple ``(distname, version, id)`` which defaults to the args given as parameters. -.. XXX Document linux_distribution()? +.. function:: linux_distribution(distname='', version='', id='') + + Tries to determine the name of the OS distribution name Returns a tuple + ``(distname, version, id)`` which defaults to the args given as parameters. + The function first looks for a *lsb-release* file in /etc and then reverts to + use distribution specific files in case no suitable files are found. And + *lsb-release* have higher priority if both files exists. .. function:: libc_ver(executable=sys.executable, lib='', version='', chunksize=2048) Index: Lib/platform.py =================================================================== --- Lib/platform.py (revision 60977) +++ Lib/platform.py (working copy) @@ -34,6 +34,7 @@ # # # +# 1.0.7 - fixed dist() unpredictable result # 1.0.6 - added linux_distribution() # 1.0.5 - fixed Java support to allow running the module on Jython # 1.0.4 - added IronPython support @@ -110,9 +111,16 @@ """ -__version__ = '1.0.6' +__version__ = '1.0.7' -import sys,string,os,re +import sys +import os +import re + +_release_filename = re.compile( r'(\w+)[-_](release|version)' ) +_release_version = re.compile( r'(.*)\s+release\s+(.*)\s+\((.*)\)\s+(.*)' ) +_lsb_variable = re.compile( r'(\w+)=(.*)' ) +_ETC_DIR = '/etc' ### Platform specific APIs @@ -186,30 +194,31 @@ """ if os.path.exists('/var/adm/inst-log/info'): # SuSE Linux stores distribution information in that file - info = open('/var/adm/inst-log/info').readlines() - distname = 'SuSE' - for line in info: - tv = string.split(line) - if len(tv) == 2: - tag,value = tv - else: - continue - if tag == 'MIN_DIST_VERSION': - version = string.strip(value) - elif tag == 'DIST_IDENT': - values = string.split(value,'-') - id = values[2] - return distname,version,id + with open('/var/adm/inst-log/info') as fp: + distname = 'SuSE' + for line in fp: + tv = line.split() + if len(tv) == 2: + tag,value = tv + else: + continue + if tag == 'MIN_DIST_VERSION': + version = value.strip() + elif tag == 'DIST_IDENT': + values = value.split('-') + id = values[2] + return distname,version,id - if os.path.exists('/etc/.installed'): + installed = os.path.join(_ETC_DIR, '.installed') + if os.path.exists(installed): # Caldera OpenLinux has some infos in that file (thanks to Colin Kong) - info = open('/etc/.installed').readlines() - for line in info: - pkg = string.split(line,'-') - if len(pkg) >= 2 and pkg[0] == 'OpenLinux': - # XXX does Caldera support non Intel platforms ? If yes, - # where can we find the needed id ? - return 'OpenLinux',pkg[1],id + with open(installed) as fp: + for line in fp: + pkg = line.split('-') + if len(pkg) >= 2 and pkg[0] == 'OpenLinux': + # XXX does Caldera support non Intel platforms ? If yes, + # where can we find the needed id ? + return 'OpenLinux',pkg[1],id if os.path.isdir('/usr/lib/setup'): # Check for slackware verson tag file (thanks to Greg Andruk) @@ -258,7 +267,7 @@ return tuple(m.groups()) # Unkown format... take the first two words - l = string.split(string.strip(firstline)) + l = firstline.strip().split() if l: version = l[0] if len(l) > 1: @@ -285,10 +294,7 @@ if parsed != output: print (input, parsed) -def linux_distribution(distname='', version='', id='', - - supported_dists=_supported_dists, - full_distribution_name=1): +def linux_distribution(distname='', version='', id=''): """ Tries to determine the name of the Linux OS distribution name. @@ -296,54 +302,82 @@ /etc and then reverts to _dist_try_harder() in case no suitable files are found. - supported_dists may be given to define the set of Linux - distributions to look for. It defaults to a list of currently - supported Linux distributions identified by their release file - name. - - If full_distribution_name is true (default), the full - distribution read from the OS is returned. Otherwise the short - name taken from supported_dists is used. - Returns a tuple (distname,version,id) which default to the args given as parameters. """ + + # found will contains elements that have been set from _ETC_DIR files parsing + found = {} + # First read _ETC_DIR/lsb-release to initialize distname, version and id + lsb = os.path.join(_ETC_DIR, "lsb-release") + if os.path.exists(lsb): + with open(lsb, 'r') as fp: + for line in fp: + m = _lsb_variable.match( line ) + if m: + n, v = m.groups() + if v: + if n=='DISTRIB_ID': found[ 'distname' ] = v + elif n == 'DISTRIB_RELEASE': found[ 'version' ] = v + elif n == 'DISTRIB_CODENAME': found[ 'id' ] = v + try: - etc = os.listdir('/etc') + etc = os.listdir(_ETC_DIR) except os.error: # Probably not a Unix system - return distname,version,id - etc.sort() - for file in etc: - m = _release_filename.match(file) - if m is not None: - _distname,dummy = m.groups() - if _distname in supported_dists: - distname = _distname - break - else: - return _dist_try_harder(distname,version,id) - - # Read the first line - f = open('/etc/'+file, 'r') - firstline = f.readline() - f.close() - _distname, _version, _id = _parse_release_file(firstline) - - if _distname and full_distribution_name: - distname = _distname - if _version: - version = _version - if _id: - id = _id - return distname, version, id + return ( found.get('distname', distname), + found.get( 'version', version ), + found.get( 'id', id ) ) + # Look for other release files in _ETC_DIR + filesAndMatch = [] + for f in etc: + file = os.path.join(_ETC_DIR, f) + if file == lsb or os.path.islink(file) or not os.path.isfile(file): + continue + m = _release_filename.match(f) + if m: + filesAndMatch.append( ( file, m ) ) + + if not filesAndMatch: + # No release file found + return _dist_try_harder( found.get('distname',distname), + found.get( 'version', version ), + found.get( 'id', id ) ) + + if len( filesAndMatch ) > 1: + # If there are several _ETC_DIR/*-release or _ETC_DIR/*-version files + # they are sorted so that the result is always the same whatever + # the order returned by os.listdir(_ETC_DIR) + filesAndMatch.sort() + + file, match = filesAndMatch[ 0 ] + + if not found.has_key( 'distname' ): + found[ 'distname' ] = match.group( 1 ) + + with open( file, 'r' ) as fp: + firstline = fp.readline() + + _distname, _version, _id = _parse_release_file(firstline) + + if _distname: + if not found.has_key( 'distname' ): + found[ 'distname' ] = _distname.replace ( ' ', '' ) + if _version: + if not found.has_key( 'version' ): + found[ 'version' ] = _version + if _id: + if not found.has_key( 'id' ): + found[ 'id' ] = _id + + return ( found.get('distname',distname), + found.get( 'version', version ), + found.get( 'id', id ) ) # To maintain backwards compatibility: -def dist(distname='',version='',id='', - - supported_dists=_supported_dists): +def dist(distname='',version='',id=''): """ Tries to determine the name of the Linux OS distribution name. @@ -355,9 +389,7 @@ args given as parameters. """ - return linux_distribution(distname, version, id, - supported_dists=supported_dists, - full_distribution_name=0) + return linux_distribution(distname, version, id) class _popen: @@ -451,7 +483,7 @@ """ Normalize the version and build strings and return a single version string using the format major.minor.build (or patchlevel). """ - l = string.split(version,'.') + l = version.split('.') if build: l.append(build) try: @@ -460,7 +492,7 @@ strings = l else: strings = map(str,ints) - version = string.join(strings[:3],'.') + version = '.'.join(strings[:3]) return version _ver_output = re.compile(r'(?:([\w ]+) ([\w.]+) ' @@ -505,7 +537,7 @@ return system,release,version # Parse the output - info = string.strip(info) + info = info.strip() m = _ver_output.match(info) if m is not None: system,release,version = m.groups() @@ -776,7 +808,7 @@ # These releases use the old name SunOS return system,release,version # Modify release (marketing release = SunOS release - 3) - l = string.split(release,'.') + l = release.split('.') if l: try: major = int(l[0]) @@ -785,7 +817,7 @@ else: major = major - 3 l[0] = str(major) - release = string.join(l,'.') + release = '.'.join(l) if release < '6': system = 'Solaris' else: @@ -815,29 +847,19 @@ """ Helper to format the platform string in a filename compatible format e.g. "system-version-machine". """ - # Format the platform string - platform = string.join( - map(string.strip, - filter(len, args)), - '-') + platform = '-'.join(s.strip() for s in args) # Cleanup some possible filename obstacles... - replace = string.replace - platform = replace(platform,' ','_') - platform = replace(platform,'/','-') - platform = replace(platform,'\\','-') - platform = replace(platform,':','-') - platform = replace(platform,';','-') - platform = replace(platform,'"','-') - platform = replace(platform,'(','-') - platform = replace(platform,')','-') + platform = platform.replace(' ', '_') + for s in ('/', '\\', ':', ';', '"', "'", '(', ')'): + platform = platform.replace(s, '-') # No need to report 'unknown' information... - platform = replace(platform,'unknown','') + platform = platform.replace('unknown', '') # Fold '--'s and remove trailing '-' while 1: - cleaned = replace(platform,'--','-') + cleaned = platform.replace('--', '-') if cleaned == platform: break platform = cleaned @@ -899,7 +921,7 @@ f = os.popen('uname %s 2> /dev/null' % option) except (AttributeError,os.error): return default - output = string.strip(f.read()) + output = f.read().strip() rc = f.close() if not output or rc: return default @@ -921,7 +943,7 @@ f = os.popen('file %s 2> /dev/null' % target) except (AttributeError,os.error): return default - output = string.strip(f.read()) + output = f.read().strip() rc = f.close() if not output or rc: return default @@ -1102,7 +1124,7 @@ elif system[:4] == 'java': release,vendor,vminfo,osinfo = java_ver() system = 'Java' - version = string.join(vminfo,', ') + version = ', '.join(vminfo) if not version: version = vendor @@ -1147,7 +1169,7 @@ if processor == 'unknown': processor = '' - # normalize name + # normalize name if system == 'Microsoft' and release == 'Windows': system = 'Windows' release = 'Vista' @@ -1311,10 +1333,10 @@ builddate = builddate + ' ' + buildtime # Add the patchlevel version if missing - l = string.split(version, '.') + l = version.split('.') if len(l) == 2: l.append('0') - version = string.join(l, '.') + version = '.'.join(l) # Build and cache the result result = (name, version, branch, revision, buildno, builddate, compiler) @@ -1371,7 +1393,7 @@ """ if hasattr(sys, 'version_info'): return sys.version_info[:3] - return tuple(string.split(_sys_version()[1], '.')) + return tuple(_sys_version()[1].split('.')) def python_branch(): Index: Lib/test/platform/redhat_nahant/redhat-release =================================================================== --- Lib/test/platform/redhat_nahant/redhat-release (revision 0) +++ Lib/test/platform/redhat_nahant/redhat-release (revision 0) @@ -0,0 +1 @@ +Red Hat Enterprise Linux AS release 4 (Nahant) Index: Lib/test/platform/redhat_nahant/lsb-release =================================================================== --- Lib/test/platform/redhat_nahant/lsb-release (revision 0) +++ Lib/test/platform/redhat_nahant/lsb-release (revision 0) @@ -0,0 +1 @@ +LSB_VERSION="1.3" Index: Lib/test/platform/centos_release5/redhat-release =================================================================== --- Lib/test/platform/centos_release5/redhat-release (revision 0) +++ Lib/test/platform/centos_release5/redhat-release (revision 0) @@ -0,0 +1 @@ +CentOS release 5 (Final) Index: Lib/test/platform/debian_etch/debian_version =================================================================== --- Lib/test/platform/debian_etch/debian_version (revision 0) +++ Lib/test/platform/debian_etch/debian_version (revision 0) @@ -0,0 +1 @@ +4.0 Index: Lib/test/platform/README.txt =================================================================== --- Lib/test/platform/README.txt (revision 0) +++ Lib/test/platform/README.txt (revision 0) @@ -0,0 +1,2 @@ +This directory contains *-release files for platform.dist() tests. + Index: Lib/test/platform/ubuntu_gutsy/debian_version =================================================================== --- Lib/test/platform/ubuntu_gutsy/debian_version (revision 0) +++ Lib/test/platform/ubuntu_gutsy/debian_version (revision 0) @@ -0,0 +1 @@ +lenny/sid Index: Lib/test/platform/ubuntu_gutsy/lsb-release =================================================================== --- Lib/test/platform/ubuntu_gutsy/lsb-release (revision 0) +++ Lib/test/platform/ubuntu_gutsy/lsb-release (revision 0) @@ -0,0 +1,4 @@ +DISTRIB_ID=Ubuntu +DISTRIB_RELEASE=7.10 +DISTRIB_CODENAME=gutsy +DISTRIB_DESCRIPTION="Ubuntu 7.10" Index: Lib/test/test_platform.py =================================================================== --- Lib/test/test_platform.py (revision 60977) +++ Lib/test/test_platform.py (working copy) @@ -1,7 +1,33 @@ +import os import unittest from test import test_support import platform +testdir = os.path.dirname(__file__) or os.curdir +platform_dir = os.path.join(testdir, "platform") + +dist_expected = { + 'ubuntu_gutsy' : ('Ubuntu', '7.10', 'gutsy'), + 'redhat_nahant' : ('redhat', '4', 'Nahant'), + 'debian_etch' : ('debian', '4.0', ''), + 'centos_release5' : ('redhat', '5', 'Final'), + } + +class PlatformDistTest(unittest.TestCase): + + def setUp(self): + self.etc = platform._ETC_DIR + + def tearDown(self): + platform._ETC_DIR = self.etc + + def test_dist(self): + for subdir, expected in dist_expected.items(): + directory = os.path.join(platform_dir, subdir) + platform._ETC_DIR = directory + result = platform.dist() + self.assertEqual(result, expected) + class PlatformTest(unittest.TestCase): def test_architecture(self): res = platform.architecture() @@ -72,7 +98,8 @@ def test_main(): test_support.run_unittest( - PlatformTest + PlatformTest, + PlatformDistTest ) if __name__ == '__main__':