Server IP : 85.214.239.14 / Your IP : 18.116.49.23 Web Server : Apache/2.4.62 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Tue Jan 9 19:45:01 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /proc/3/cwd/usr/share/dh-python/dhpython/ |
Upload File : |
# Copyright © 2012-2013 Piotr Ożarowski <piotr@debian.org> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import logging import os import re from os.path import exists, join, split from dhpython import INTERPRETER_DIR_TPLS, PUBLIC_DIR_RE, OLD_SITE_DIRS SHEBANG_RE = re.compile(r''' (?:\#!\s*){0,1} # shebang prefix (?P<path> .*?/bin/.*?)? (?P<name> python|pypy) (?P<version> \d[\.\d]*)? (?P<debug> -dbg)? (?P<options>.*) ''', re.VERBOSE) EXTFILE_RE = re.compile(r''' (?P<name>.*?) (?:\. (?P<stableabi>abi\d+) |(?:\. (?P<soabi> (?P<impl>cpython|pypy) - (?P<ver>\d{2,}) (?P<flags>[a-z]*) )? (?: (?:(?<!\.)-)? # minus sign only if soabi is defined (?P<multiarch>[^/]*?) )? ))? (?P<debug>_d)? \.so$''', re.VERBOSE) log = logging.getLogger('dhpython') class Interpreter: """ :attr path: /usr/bin/ in most cases :attr name: pypy or python (even for python3 and python-dbg) or empty string :attr version: interpreter's version :attr debug: -dbg version of the interpreter :attr impl: implementation (cpytho2, cpython3 or pypy) :attr options: options parsed from shebang :type path: str :type name: str :type version: Version or None :type debug: bool :type impl: str :type options: tuple """ path = '/usr/bin/' name = 'python' version = None debug = False impl = '' options = () _cache = {} def __init__(self, value=None, path=None, name=None, version=None, debug=None, impl=None, options=None): params = locals() del params['self'] del params['value'] if isinstance(value, Interpreter): for key in params.keys(): if params[key] is None: params[key] = getattr(value, key) elif value: if value.replace('.', '').isdigit() and not version: # version string params['version'] = Version(value) else: # shebang or other string for key, val in self.parse(value).items(): # prefer values passed to constructor over shebang ones: if params[key] is None: params[key] = val for key, val in params.items(): if val is not None: setattr(self, key, val) elif key == 'version': setattr(self, key, val) def __setattr__(self, name, value): if name == 'name': if value not in ('python', 'pypy', ''): raise ValueError("interpreter not supported: %s" % value) if value == 'python': if self.version: if self.version.major == 3: self.__dict__['impl'] = 'cpython3' else: self.__dict__['impl'] = 'cpython2' elif value == 'pypy': self.__dict__['impl'] = 'pypy' elif name == 'version' and value is not None: value = Version(value) if not self.impl and self.name == 'python': if value.major == 3: self.impl = 'cpython3' else: self.impl = 'cpython2' if name in ('path', 'name', 'impl', 'options') and value is None: pass elif name == 'debug': self.__dict__[name] = bool(value) else: self.__dict__[name] = value def __repr__(self): result = self.path if not result.endswith('/'): result += '/' result += self._vstr(self.version) if self.options: result += ' ' + ' '.join(self.options) return result def __str__(self): return self._vstr(self.version) def _vstr(self, version=None, consider_default_ver=False): if self.impl == 'pypy': # TODO: will Debian support more than one PyPy version? return self.name version = version or self.version or '' if consider_default_ver and (not version or version == self.default_version): version = '3' if self.impl == 'cpython3' else '2' if self.debug: return 'python{}-dbg'.format(version) return self.name + str(version) def binary(self, version=None): return '{}{}'.format(self.path, self._vstr(version)) @property def binary_dv(self): """Like binary(), but returns path to default intepreter symlink if version matches default one for given implementation. """ return '{}{}'.format(self.path, self._vstr(consider_default_ver=True)) @property def default_version(self): if self.impl: return default(self.impl) @staticmethod def parse(shebang): """Return dict with parsed shebang >>> sorted(Interpreter.parse('/usr/bin/python3.2-dbg').items()) [('debug', '-dbg'), ('name', 'python'), ('options', ()), ('path', '/usr/bin/'), ('version', '3.2')] >>> sorted(Interpreter.parse('#! /usr/bin/python3.2').items()) [('debug', None), ('name', 'python'), ('options', ()), ('path', '/usr/bin/'), ('version', '3.2')] >>> sorted(Interpreter.parse('/usr/bin/python3.2-dbg --foo --bar').items()) [('debug', '-dbg'), ('name', 'python'), ('options', ('--foo', '--bar')),\ ('path', '/usr/bin/'), ('version', '3.2')] """ result = SHEBANG_RE.search(shebang) if not result: return {} result = result.groupdict() if 'options' in result: # TODO: do we need "--key value" here? result['options'] = tuple(result['options'].split()) if result['name'] == 'python' and result['version'] is None: result['version'] = '2' return result @classmethod def from_file(cls, fpath): """Read file's shebang and parse it.""" interpreter = Interpreter() with open(fpath, 'rb') as fp: data = fp.read(96) if b"\0" in data: raise ValueError('cannot parse binary file') # make sure only first line is checkeed data = str(data, 'utf-8').split('\n')[0] if not data.startswith('#!'): raise ValueError("doesn't look like a shebang: %s" % data) parsed = cls.parse(data) if not parsed: raise ValueError("doesn't look like a shebang: %s" % data) for key, val in parsed.items(): setattr(interpreter, key, val) return interpreter def sitedir(self, package=None, version=None, gdb=False): """Return path to site-packages directory. Note that returned path is not the final location of .py files >>> i = Interpreter('python') >>> i.sitedir(version='3.1') '/usr/lib/python3/dist-packages/' >>> i.sitedir(version='2.5') '/usr/lib/python2.5/site-packages/' >>> i.sitedir(version=Version('2.7')) '/usr/lib/python2.7/dist-packages/' >>> i.sitedir(version='3.1', gdb=True, package='python3-foo') 'debian/python3-foo/usr/lib/debug/usr/lib/python3/dist-packages/' >>> i.sitedir(version=Version('3.2')) '/usr/lib/python3/dist-packages/' """ try: version = Version(version or self.version) except Exception as err: raise ValueError("cannot find valid version: %s" % err) if self.impl == 'pypy': path = '/usr/lib/pypy/dist-packages/' elif version << Version('2.6'): path = "/usr/lib/python%s/site-packages/" % version elif version << Version('3.0'): path = "/usr/lib/python%s/dist-packages/" % version else: path = '/usr/lib/python3/dist-packages/' if gdb: path = "/usr/lib/debug%s" % path if package: path = "debian/%s%s" % (package, path) return path def old_sitedirs(self, package=None, version=None, gdb=False): """Return deprecated paths to site-packages directories.""" try: version = Version(version or self.version) except Exception as err: raise ValueError("cannot find valid version: %s" % err) result = [] for item in OLD_SITE_DIRS.get(self.impl, []): if isinstance(item, str): result.append(item.format(version)) else: res = item(version) if res is not None: result.append(res) if gdb: result = ['/usr/lib/debug{}'.format(i) for i in result] if self.impl.startswith('cpython'): result.append('/usr/lib/debug/usr/lib/pyshared/python{}'.format(version)) if package: result = ['debian/{}{}'.format(package, i) for i in result] return result def parse_public_dir(self, path): """Return version assigned to site-packages path or True is it's unversioned public dir.""" match = PUBLIC_DIR_RE[self.impl].match(path) if match: vers = match.groups(0) if vers and vers[0]: return Version(vers) return True def should_ignore(self, path): """Return True if path is used by another interpreter implementation.""" cache_key = 'should_ignore_{}'.format(self.impl) if cache_key not in self.__class__._cache: expr = [v for k, v in INTERPRETER_DIR_TPLS.items() if k != self.impl] regexp = re.compile('|'.join('({})'.format(i) for i in expr)) self.__class__._cache[cache_key] = regexp else: regexp = self.__class__._cache[cache_key] return regexp.search(path) def cache_file(self, fpath, version=None): """Given path to a .py file, return path to its .pyc/.pyo file. This function is inspired by Python 3.2's imp.cache_from_source. :param fpath: path to file name :param version: Python version >>> i = Interpreter('python') >>> i.cache_file('foo.py', Version('3.1')) 'foo.pyc' >>> i.cache_file('bar/foo.py', '3.8') # doctest: +SKIP 'bar/__pycache__/foo.cpython-38.pyc' """ version = Version(version or self.version) last_char = 'o' if '-O' in self.options else 'c' if version <= Version('3.1'): return fpath + last_char fdir, fname = split(fpath) if not fname.endswith('.py'): fname += '.py' return join(fdir, '__pycache__', "%s.%s.py%s" % (fname[:-3], self.magic_tag(version), last_char)) def magic_number(self, version=None): """Return magic number.""" version = Version(version or self.version) if self.impl == 'cpython2': return '' result = self._execute('import imp; print(imp.get_magic())', version) return eval(result) def magic_tag(self, version=None): """Return Python magic tag (used in __pycache__ dir to tag files). >>> i = Interpreter('python') >>> i.magic_tag(version='3.8') # doctest: +SKIP 'cpython-38' """ version = Version(version or self.version) if self.impl.startswith('cpython') and version << Version('3.2'): return '' return self._execute('import imp; print(imp.get_tag())', version) def multiarch(self, version=None): """Return multiarch tag.""" version = Version(version or self.version) try: soabi, multiarch = self._get_config(version)[:2] except Exception: log.debug('cannot get multiarch', exc_info=True) # interpreter without multiarch support return '' return multiarch def stableabi(self, version=None): version = Version(version or self.version) # stable ABI was introduced in Python 3.3 if self.impl == 'cpython3' and version >> Version('3.2'): return 'abi{}'.format(version.major) def soabi(self, version=None): """Return SOABI flag (used to in .so files).""" version = Version(version or self.version) # NOTE: it's not the same as magic_tag try: soabi, multiarch = self._get_config(version)[:2] except Exception: log.debug('cannot get soabi', exc_info=True) # interpreter without soabi support return '' return soabi @property def include_dir(self): """Return INCLUDE_DIR path. >>> Interpreter('python2.7').include_dir # doctest: +SKIP '/usr/include/python2.7' >>> Interpreter('python3.8-dbg').include_dir # doctest: +SKIP '/usr/include/python3.8d' """ if self.impl == 'pypy': return '/usr/lib/pypy/include' try: result = self._get_config()[2] if result: return result except Exception: result = '' log.debug('cannot get include path', exc_info=True) result = '/usr/include/{}'.format(self.name) version = self.version if self.debug: if version >= '3.8': result += 'd' elif version << '3.3': result += '_d' else: result += 'dm' else: if version >= '3.8': pass elif version >> '3.2': result += 'm' elif version == '3.2': result += 'mu' return result @property def symlinked_include_dir(self): """Return path to symlinked include directory.""" if self.impl in ('cpython2', 'pypy') or self.debug \ or self.version >> '3.7' or self.version << '3.3': # these interpreters do not provide symlink, # others provide it in libpython3.X-dev return try: result = self._get_config()[2] if result: if result.endswith('m'): return result[:-1] else: # there's include_dir, but no "m" return except Exception: result = '/usr/include/{}'.format(self.name) log.debug('cannot get include path', exc_info=True) return result @property def library_file(self): """Return libfoo.so file path.""" if self.impl == 'pypy': return '' libpl, ldlibrary = self._get_config()[3:5] if ldlibrary.endswith('.a'): # python3.1-dbg, python3.2, python3.2-dbg returned static lib ldlibrary = ldlibrary.replace('.a', '.so') if libpl and ldlibrary: return join(libpl, ldlibrary) raise Exception('cannot find library file for {}'.format(self)) def check_extname(self, fname, version=None): """Return extension file name if file can be renamed.""" if not version and not self.version: return version = Version(version or self.version) if '/' in fname: fdir, fname = fname.rsplit('/', 1) # in case full path was passed else: fdir = '' info = EXTFILE_RE.search(fname) if not info: return info = info.groupdict() if info['ver'] and (not version or version.minor is None): # get version from soabi if version is not set of only major # version number is set version = Version("%s.%s" % (info['ver'][0], info['ver'][1])) if info['stableabi']: # files with stable ABI in name don't need changes return if info['debug'] and self.debug is False: # do not change Python 2.X extensions already marked as debug # (the other way around is acceptable) return if info['soabi'] and info['multiarch']: # already tagged, nothing we can do here return try: soabi, multiarch = self._get_config(version)[:2] except Exception: log.debug('cannot get soabi/multiarch', exc_info=True) return if info['soabi'] and soabi and info['soabi'] != soabi: return tmp_soabi = info['soabi'] or soabi tmp_multiarch = info['multiarch'] or multiarch result = info['name'] if result.endswith('module') and result != 'module' and ( self.impl == 'cpython3' and version >> '3.2' or self.impl == 'cpython2' and version == '2.7'): result = result[:-6] if tmp_soabi: result = "{}.{}".format(result, tmp_soabi) if tmp_multiarch and not (self.impl == 'cpython3' and version << '3.3') and tmp_multiarch not in soabi: result = "{}-{}".format(result, tmp_multiarch) elif self.impl == 'cpython2' and version == '2.7' and tmp_multiarch: result = "{}.{}".format(result, tmp_multiarch) if self.debug and self.impl == 'cpython2': result += '_d' result += '.so' if fname == result: return return join(fdir, result) def suggest_pkg_name(self, name): """Suggest binary package name with for given library name >>> Interpreter('python3.1').suggest_pkg_name('foo') 'python3-foo' >>> Interpreter('python3.8').suggest_pkg_name('foo_bar') 'python3-foo-bar' >>> Interpreter('python2.7-dbg').suggest_pkg_name('bar') 'python-bar-dbg' """ name = name.replace('_', '-') if self.impl == 'pypy': return 'pypy-{}'.format(name) version = '3' if self.impl == 'cpython3' else '' result = 'python{}-{}'.format(version, name) if self.debug: result += '-dbg' return result def _get_config(self, version=None): version = Version(version or self.version) # sysconfig module is available since Python 3.2 # (also backported to Python 2.7) if self.impl == 'pypy' or self.impl.startswith('cpython') and ( version >> '2.6' and version << '3' or version >> '3.1' or version == '3'): cmd = 'import sysconfig as s;' else: cmd = 'from distutils import sysconfig as s;' cmd += 'print("__SEP__".join(i or "" ' \ 'for i in s.get_config_vars('\ '"SOABI", "MULTIARCH", "INCLUDEPY", "LIBPL", "LDLIBRARY")))' conf_vars = self._execute(cmd, version).split('__SEP__') if conf_vars[1] in conf_vars[0]: # Python >= 3.5 includes MILTIARCH in SOABI conf_vars[0] = conf_vars[0].replace("-%s" % conf_vars[1], '') try: conf_vars[1] = os.environ['DEB_HOST_MULTIARCH'] except KeyError: pass return conf_vars def _execute(self, command, version=None, cache=True): version = Version(version or self.version) exe = "{}{}".format(self.path, self._vstr(version)) command = "{} -c '{}'".format(exe, command.replace("'", "\'")) if cache and command in self.__class__._cache: return self.__class__._cache[command] if not exists(exe): raise Exception("cannot execute command due to missing " "interpreter: %s" % exe) output = execute(command) if output['returncode'] != 0: log.debug(output['stderr']) raise Exception('{} failed with status code {}'.format(command, output['returncode'])) result = output['stdout'].splitlines() if len(result) == 1: result = result[0] if cache: self.__class__._cache[command] = result return result # due to circular imports issue from dhpython.tools import execute from dhpython.version import Version, default