|
|
|
|
|
|
|
|
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 |
|
|
|