toss-hwp / tools /discover.python /discover_python.py
seawolf2357's picture
Add tools
a65138c verified
# -*- coding: utf-8 -*-
import logging
import os
import os.path
import sys
import re
logger = logging.getLogger(__name__)
EXPOSE_NAMES = ('executable', 'version', 'prefix', 'exec_prefix', 'through')
def wellknown_locations():
if sys.platform == 'win32':
base = 'c:\\'
for name in os.listdir(base):
name = name.lower()
if name.startswith('python'):
shortversion = name[len('python'):]
m = re.match('[23][0-9]', shortversion)
if m:
yield base + name
elif 'PYTHONZ_ROOT' in os.environ:
pythonz_root = os.environ['PYTHONZ_ROOT']
pythons = os.path.join(pythonz_root, 'pythons')
for item in os.listdir(pythons):
yield os.path.join(pythons, item)
def discover_python(in_wellknown=True, in_path=True):
if in_wellknown:
for found in search_in_wellknown_locations():
yield found
if in_path:
for found in search_in_path():
yield found
def search_in_wellknown_locations():
for location in wellknown_locations():
if sys.platform == 'win32':
founds = contains_python(location)
else:
founds = contains_python_in_bin(location)
for found in founds:
found['through'] = 'WELLKNOWN_LOCATION'
yield found
def search_in_path():
if 'PATH' in os.environ:
path = os.environ['PATH']
path = path.split(os.pathsep)
for dir in path:
for found in contains_python(dir):
found['through'] = 'PATH'
# resolve symlinks
resolved = os.path.realpath(found['executable'])
found['executable'] = resolved
yield found
def contains_python_in_bin(dir):
bindir = os.path.join(dir, 'bin')
return contains_python(bindir)
def contains_python(dir):
vers = {
2: [3, 4, 5, 6, 7],
3: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
}
names = (('python%d.%d' % (major, minor))
for major in reversed(sorted(vers))
for minor in reversed(sorted(vers[major])))
names = list(names) + ['python']
for name in names:
executable = executable_in_dir(name, dir)
if executable:
found = executable_is_python(executable)
if found:
yield found
def executable_in_dir(name, dir):
assert name == os.path.basename(name)
if sys.platform == 'win32':
name += '.exe'
path = os.path.join(dir, name)
if not os.path.exists(path):
return
return path
def executable_is_python(executable):
from subprocess import Popen
from subprocess import PIPE
cmd = '''
import os, sys
print(sys.hexversion)
print(os.pathsep.join([sys.prefix, sys.exec_prefix]))
'''.strip().replace('\n', ';')
args = [executable, '-c', cmd]
env = dict(os.environ)
for k in ('PYTHONPATH', 'PYTHONHOME'):
if k in env:
del env[k]
try:
p = Popen(args, stdout=PIPE, env=env)
lines = p.stdout.read().split('\n')
p.wait()
ver = int(lines[0])
ver_major = str(ver >> 24 & 0xff)
ver_minor = str(ver >> 16 & 0xff)
ver_patch = str(ver >> 8 & 0xff)
ver = ver_major, ver_minor, ver_patch
version = '.'.join(ver)
prefix, exec_prefix = lines[1].split(os.pathsep)
return dict(executable=executable, version=version,
prefix=prefix, exec_prefix=exec_prefix)
except Exception, e:
logger.error('popen failed: %s', args)
logger.exception(e)
def log_discovered(qualify, found, names, log=logger.info):
for item in found:
msg = qualify + ':'
for name in names:
if name in item:
msg += ' %s=%s' % (name, item[name])
log(msg)
yield item
def expose_options(options, names, found, not_found, logger=logger):
for name in names:
if name in found:
value = found[name]
if name in options:
if value != options[name]:
logger.info('(updating) %s = %s', name, value)
options[name] = value
else:
logger.info('(preserving) %s = %s', name, value)
else:
logger.info('(exposing) %s = %s', name, value)
options[name] = value
else:
if name not in options:
options[name] = value = not_found
logger.info('(exposing) %s = %s', name, value)
class Discover(object):
''' Discover Python and provide its location.
'''
def __init__(self, buildout, name, options):
from zc.buildout import UserError
self.__logger = logger = logging.getLogger(name)
for k, v in options.items():
logger.info('%s: %r', k, v)
self.__recipe = options['recipe']
not_found = options.get('not-found', 'not-found')
version = options.get('version', '').strip()
if 'location' in options:
# if location is explicitly specified, it must contains java
# executable.
for found in contains_python_in_bin(options['location']):
if not version or found['version'].startswith(version):
# Python found, no further discovery required.
options['executable'] = found['executable']
return
raise UserError('Python not found at %s' % options['location'])
in_wellknown = options.get('search-in-wellknown-places',
'true').lower().strip()
in_wellknown = in_wellknown in ('true', 'yes', '1')
in_path = options.get('search-in-path', 'true').lower().strip()
in_path = in_path in ('true', 'yes', '1')
founds = discover_python(in_wellknown=in_wellknown,
in_path=in_path)
founds = log_discovered('candidates', founds, EXPOSE_NAMES,
log=logger.debug)
if version:
# filter with version
founds = (found for found in founds
if found['version'].startswith(version))
founds = log_discovered('matching', founds, EXPOSE_NAMES,
log=logger.info)
founds = list(founds)
# location is not specified: try to discover a Python installation
if founds:
found = founds[0]
logger.info('the first-matching one will be used:')
expose_options(options, EXPOSE_NAMES, found,
not_found=not_found, logger=logger)
return
# ensure executable publishes not-found marker
expose_options(options, ['executable'], dict(), not_found=not_found,
logger=logger)
logger.warning('Python not found')
return
def install(self):
return []
update = install