| |
|
|
| 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' |
|
|
| |
| 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: |
| |
| |
| for found in contains_python_in_bin(options['location']): |
| if not version or found['version'].startswith(version): |
| |
| 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: |
| |
| founds = (found for found in founds |
| if found['version'].startswith(version)) |
| founds = log_discovered('matching', founds, EXPOSE_NAMES, |
| log=logger.info) |
| founds = list(founds) |
|
|
| |
| 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 |
|
|
| |
| expose_options(options, ['executable'], dict(), not_found=not_found, |
| logger=logger) |
| logger.warning('Python not found') |
| return |
|
|
| def install(self): |
| return [] |
|
|
| update = install |
|
|